diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff113d8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,44 @@
+# Numerous always-ignore extensions
+*.diff
+*.err
+*.orig
+*.log
+*.rej
+*.swo
+*.swp
+*.vi
+*~
+*.sass-cache
+
+# OS or Editor folders
+.DS_Store
+.cache
+.project
+.settings
+.tmproj
+nbproject
+Thumbs.db
+
+# Dreamweaver added files
+_notes
+dwsync.xml
+
+# Komodo
+*.komodoproject
+.komodotools
+
+# Folders to ignore
+.hg
+.svn
+.CVS
+intermediate
+publish
+.idea
+docs
+intermediate
+temp
+node_modules
+
+# build script local files
+build/buildinfo.properties
+build/config/buildinfo.properties
\ No newline at end of file
diff --git a/Calibre_Plugins/Ignobleepub ReadMe.txt b/Calibre_Plugins/Ignobleepub ReadMe.txt
new file mode 100644
index 0000000..3eb916b
--- /dev/null
+++ b/Calibre_Plugins/Ignobleepub ReadMe.txt
@@ -0,0 +1,67 @@
+Ignoble Epub DeDRM - ignobleepub_v02.4_plugin.zip
+
+All credit given to I♥Cabbages for the original standalone scripts.
+I had the much easier job of converting them to a calibre plugin.
+
+This plugin is meant to decrypt Barnes & Noble Epubs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary.
+
+
+Installation:
+
+Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_v02.4_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
+
+
+Configuration:
+
+Upon first installing the plugin (or upgrading from a version earlier than 0.2.0), the plugin will be unconfigured. Until you create at least one B&N key—or migrate your existing key(s)/data from an earlier version of the plugin—the plugin will not function. When unconfigured (no saved keys)... an error message will occur whenever ePubs are imported to calibre. To eliminate the error message, open the plugin's customization dialog and create/import/migrate a key (or disable/uninstall the plugin). You can get to the plugin's customization dialog by opening calibre's Preferences dialog, and clicking Plugins (under the Advanced section). Once in the Plugin Preferences, expand the "File type plugins" section and look for the "Ignoble Epub DeDRM" plugin. Highlight that plugin and click the "Customize plugin" button.
+
+Upgrading from old keys
+
+If you are upgrading from an earlier version of this plugin and have provided your name(s) and credit card number(s) as part of the old plugin's customization string, you will be prompted to migrate this data to the plugin's new, more secure, key storage method when you open the customization dialog for the first time. If you choose NOT to migrate that data, you will be prompted to save that data as a text file in a location of your choosing. Either way, this plugin will no longer be storing names and credit card numbers in plain sight (or anywhere for that matter) on your computer or in calibre. If you don't choose to migrate OR save the data, that data will be lost. You have been warned!!
+
+Upon configuring for the first time, you may also be asked if you wish to import your existing *.b64 keyfiles (if you use them) to the plugin's new key storage method. The new plugin no longer looks for keyfiles in calibre's configuration directory, so it's highly recommended that you import any existing keyfiles when prompted ... but you always have the ability to import existing keyfiles anytime you might need/want to.
+
+If you have upgraded from an earlier version of the plugin, the above instructions may be all you need to do to get the new plugin up and running. Continue reading for new-key generation and existing-key management instructions.
+
+Creating New Keys:
+
+On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.
+
+* Unique Key Name: this is a unique name you choose to help you identify the key after it's created. This name will show in the list of configured keys. Choose something that will help you remember the data (name, cc#) it was created with.
+* Your Name: Your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. It is usually just your first name and last name separated by a space. This name will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.
+* Credit Card number: this is the credit card number that was set as default with Barnes & Noble at the time of download. Nothing fancy here; no dashes or spaces ... just the 16 (15?) digits. Again... this number will not be stored anywhere on your computer or in calibre. It will only be used in the creation of the one-way hash/key that's stored in the preferences.
+Click the 'OK" button to create and store the generated key. Or Cancel if you didn't want to create a key.
+
+Deleting Keys:
+
+On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a red "X". Clicking this button will delete the highlighted key in the list. You will be prompted once to be sure that's what you truly mean to do. Once gone, it's permanently gone.
+
+Exporting Keys:
+
+On the right-hand side of the plugin's customization dialog, you will see a button with an icon that looks like a computer's hard-drive. Use this button to export the highlighted key to a file (*.b64). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.
+
+Importing Existing Keyfiles:
+
+At the bottom-left of the plugin's customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing *.b64 keyfiles. Used for migrating keyfiles from older versions of the plugin (or keys generated with the original I <3 Cabbages script), or moving keyfiles from computer to computer, or restoring a backup. Some very basic validation is done to try to avoid overwriting already configured keys with incoming, imported keyfiles with the same base file name, but I'm sure that could be broken if someone tried hard. Just take care when importing.
+
+Once done creating/importing/exporting/deleting decryption keys; click "OK" to exit the customization dialogue (the cancel button will actually work the same way here ... at this point all data/changes are committed already, so take your pick).
+
+Troubleshooting:
+
+If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook to calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
+
+On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
+
+On Windows, open a terminal/command window. (Start/Run… and then type 'cmd' (without the 's) as the program to run).
+On Macintosh, open the Terminal application (in your Utilities folder).
+On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
+
+You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running.
+
+Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
+
+Now copy the output from the terminal window.
+On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
+On Macintosh and Linux, just use the normal text select and copy commands.
+
+Paste the information into a comment at my blog, describing your problem.
diff --git a/Calibre_Plugins/Ineptepub ReadMe.txt b/Calibre_Plugins/Ineptepub ReadMe.txt
new file mode 100644
index 0000000..9dfdf57
--- /dev/null
+++ b/Calibre_Plugins/Ineptepub ReadMe.txt
@@ -0,0 +1,50 @@
+Inept Epub DeDRM - ineptepub_v01.9_plugin.zip
+
+All credit given to I♥Cabbages for the original standalone scripts.
+I had the much easier job of converting them to a Calibre plugin.
+
+This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python and PyCrypto already installed, but they aren't necessary.
+
+
+Installation:
+
+Go to Calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Cahnge calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptepub_v01.9_plugin.zip) and click the 'Add' button. you're done.
+
+Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
+
+
+Configuration:
+
+When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS). If successful, it will create 'calibre-adeptkey[number].der' file(s) and save them in Calibre's configuration directory. It will use those files and any other '*.der' files in any decryption attempts. If there is already at least one 'calibre-adept*.der' file in the directory, the plugin won't attempt to find the Adobe Digital Editions installation keys again.
+
+So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading.
+
+If you already have keyfiles generated with I♥Cabbages' ineptkey.pyw script, you can put those keyfiles in Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre configuration directory' button. Paste your keyfiles in there. Just make sure that they have different names and are saved with the '.der' extension (like the ineptkey script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
+
+Since there is no Linux version of Adobe Digital Editions, Linux users will have to obtain a keyfile through other methods and put the file in Calibre's configuration directory.
+
+All keyfiles with a '.der' extension found in Calibre's configuration directory will be used to attempt to decrypt a book.
+
+
+** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin.
+
+
+Troubleshooting:
+
+If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook to calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
+
+On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
+
+On Windows, open a terminal/command window. (Start/Run… and then type ‘cmd’ (without the ‘s) as the program to run).
+On Macintosh, open the Terminal application (in your Utilities folder).
+On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
+
+You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running.
+
+Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
+
+Now copy the output from the terminal window.
+On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
+On Macintosh and Linux, just use the normal text select and copy commands.
+
+Paste the information into a comment at my blog, describing your problem.
\ No newline at end of file
diff --git a/Calibre_Plugins/Ineptpdf ReadMe.txt b/Calibre_Plugins/Ineptpdf ReadMe.txt
new file mode 100644
index 0000000..ab5a510
--- /dev/null
+++ b/Calibre_Plugins/Ineptpdf ReadMe.txt
@@ -0,0 +1,48 @@
+Inept PDF Plugin - ineptpdf_v01.8_plugin.zip
+
+All credit given to I♥Cabbages for the original standalone scripts.
+I had the much easier job of converting them to a Calibre plugin.
+
+This plugin is meant to decrypt Adobe Digital Edition PDFs that are protected with Adobe's Adept encryption. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. It will still work if you have Python, PyCrypto and/or OpenSSL already installed, but they aren't necessary.
+
+
+Installation:
+
+Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptpdf_v01.8_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
+
+
+Configuration:
+
+When first run, the plugin will attempt to find your Adobe Digital Editions installation (on Windows and Mac OS). If successful, it will create 'calibre-adeptkey[number].der' file(s) and save them in Calibre's configuration directory. It will use those files and any other '*.der' files in any decryption attempts. If there is already at least one 'calibre-adept*.der' file in the directory, the plugin won't attempt to find the Adobe Digital Editions installation keys again.
+
+So if you have Adobe Digital Editions installation installed on the same machine as Calibre... you are ready to go. If not... keep reading.
+
+If you already have keyfiles generated with I♥Cabbages' ineptkey.pyw script, you can put those keyfiles in Calibre's configuration directory. The easiest way to find the correct directory is to go to Calibre's Preferences page... click on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre configuration directory' button. Paste your keyfiles in there. Just make sure that
+they have different names and are saved with the '.der' extension (like the ineptkey script produces). This directory isn't touched when upgrading Calibre, so it's quite safe to leave them there.
+
+Since there is no Linux version of Adobe Digital Editions, Linux users will have to obtain a keyfile through other methods and put the file in Calibre's configuration directory.
+
+All keyfiles with a '.der' extension found in Calibre's configuration directory will be used to attempt to decrypt a book.
+
+** NOTE ** There is no plugin customization data for the Inept PDF plugin.
+
+
+Troubleshooting:
+
+If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook to calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
+
+On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
+
+On Windows, open a terminal/command window. (Start/Run… and then type ‘cmd’ (without the ‘s) as the program to run).
+On Macintosh, open the Terminal application (in your Utilities folder).
+On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
+
+You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running.
+
+Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
+
+Now copy the output from the terminal window.
+On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
+On Macintosh and Linux, just use the normal text select and copy commands.
+
+Paste the information into a comment at my blog, describing your problem.
diff --git a/Calibre_Plugins/K4MobiDeDRM ReadMe.txt b/Calibre_Plugins/K4MobiDeDRM ReadMe.txt
new file mode 100644
index 0000000..d080d49
--- /dev/null
+++ b/Calibre_Plugins/K4MobiDeDRM ReadMe.txt
@@ -0,0 +1,55 @@
+K4MobiDeDRM_v04.7_plugin.zip
+
+Credit given to The Dark Reverser for the original standalone script. Credit also to the many people who have updated and expanded that script since then.
+
+Plugin for K4PC, K4Mac, eInk Kindles and Mobipocket.
+
+This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM and K4X plugins. If you install this plugin, those plugins should be removed.
+
+This plugin is meant to remove the DRM from .prc, .mobi, .azw, .azw1, .azw3, .azw4 and .tpz ebooks. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books.
+
+
+Installation:
+
+Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (K4MobiDeDRM_v04.7_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
+
+Make sure that you delete any old versions of the plugin. They might interfere with the operation of the new one.
+
+
+Configuration:
+
+Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page.
+
+If you have an eInk Kindle enter the 16 character serial number (these all begin a "B") in the serial numbers field. The easiest way to make sure that you have the serial number right is to copy it from your Amazon account pages (the "Manage Your Devices" page). If you have more than one eInk Kindle, you can enter multiple serial numbers separated by commas.
+
+If you have Mobipocket books, enter your 8 or 10 digit PID in the Mobipocket PIDs field. If you have more than one PID, separate them with commas.
+
+These configuration steps are not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.
+
+
+Linux Systems Only:
+
+If you install Kindle for PC in Wine, the plugin should be able to decode files from that Kindle for PC installation under Wine. You might need to enter a Wine Prefix if it's not already set in your Environment variables.
+
+
+Troubleshooting:
+
+If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook to calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
+
+On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
+
+On Windows, open a terminal/command window. (Start/Run… and then type 'cmd' (without the 's) as the program to run).
+On Macintosh, open the Terminal application (in your Utilities folder).
+On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
+
+You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running.
+
+Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
+
+Now copy the output from the terminal window.
+On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
+On Macintosh and Linux, just use the normal text select and copy commands.
+
+Paste the information into a comment at my blog, describing your problem.
+
+
diff --git a/Calibre_Plugins/K4MobiDeDRM_v04.7_plugin.zip b/Calibre_Plugins/K4MobiDeDRM_v04.7_plugin.zip
new file mode 100644
index 0000000..75c17f0
Binary files /dev/null and b/Calibre_Plugins/K4MobiDeDRM_v04.7_plugin.zip differ
diff --git a/Calibre_Plugins/eReaderPDB2PML ReadMe.txt b/Calibre_Plugins/eReaderPDB2PML ReadMe.txt
new file mode 100644
index 0000000..a4d3e81
--- /dev/null
+++ b/Calibre_Plugins/eReaderPDB2PML ReadMe.txt
@@ -0,0 +1,41 @@
+eReader PDB2PML - eReaderPDB2PML_v07_plugin.zip
+
+All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin.
+
+This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
+
+
+Installation:
+
+Go to Calibre's Preferences page. Do **NOT** select "Get Plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (eReaderPDB2PML_v07_plugin.zip) and click the 'Add' button. You're done.
+
+
+Please note: Calibre does not provide any immediate feedback to indicate that adding the plugin was a success. You can always click on the File-Type plugins to see if the plugin was added.
+
+
+Configuration:
+
+Highlight the plugin (eReader PDB 2 PML under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your name and last 8 digits of the credit card number separated by a comma: Your Name,12341234
+
+If you've purchased books with more than one credit card, separate the info with a colon: Your Name,12341234:Other Name,23452345
+
+
+Troubleshooting:
+
+If you find that it's not working for you (imported ebooks still have DRM), you can save a lot of time and trouble by first deleting the DRMed ebook from calibre and then trying to add the ebook to calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might as well get used to it. ;)
+
+On Macintosh only you must first run calibre, open Preferences, open Miscellaneous, and click on the “Install command line tools” button. (On Windows and Linux the command line tools are installed automatically.)
+
+On Windows, open a terminal/command window. (Start/Run… and then type ‘cmd’ (without the ‘s) as the program to run).
+On Macintosh, open the Terminal application (in your Utilities folder).
+On Linux open a command window. Hopefully all Linux users know how to do this, as I do not.
+
+You should now have a text-based command-line window open. Also have open the folder containing the ebook to be imported. Make sure that book isn’t already in calibre, and that calibre isn’t running.
+
+Now type in "calibredb add " (without the " but don’t miss that final space) and now drag the book to be imported onto the window. The full path to the book should be inserted into the command line. Now press the return/enter key. The import routines will run and produce some logging information.
+
+Now copy the output from the terminal window.
+On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it.
+On Macintosh and Linux, just use the normal text select and copy commands.
+
+Paste the information into a comment at my blog, describing your problem.
\ No newline at end of file
diff --git a/Calibre_Plugins/eReaderPDB2PML_v07_plugin.zip b/Calibre_Plugins/eReaderPDB2PML_v07_plugin.zip
new file mode 100644
index 0000000..0282220
Binary files /dev/null and b/Calibre_Plugins/eReaderPDB2PML_v07_plugin.zip differ
diff --git a/Calibre_Plugins/ignobleepub_v02.4_plugin.zip b/Calibre_Plugins/ignobleepub_v02.4_plugin.zip
new file mode 100644
index 0000000..10e26e4
Binary files /dev/null and b/Calibre_Plugins/ignobleepub_v02.4_plugin.zip differ
diff --git a/Calibre_Plugins/ineptepub_v01.9_plugin.zip b/Calibre_Plugins/ineptepub_v01.9_plugin.zip
new file mode 100644
index 0000000..216505b
Binary files /dev/null and b/Calibre_Plugins/ineptepub_v01.9_plugin.zip differ
diff --git a/Calibre_Plugins/ineptpdf_v01.8_plugin.zip b/Calibre_Plugins/ineptpdf_v01.8_plugin.zip
new file mode 100644
index 0000000..e63dcec
Binary files /dev/null and b/Calibre_Plugins/ineptpdf_v01.8_plugin.zip differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Info.plist b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Info.plist
new file mode 100644
index 0000000..d73d063
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Info.plist
@@ -0,0 +1,58 @@
+
+
+
+
+ CFBundleAllowMixedLocalizations
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeExtensions
+
+ *
+
+ CFBundleTypeOSTypes
+
+ ****
+
+ CFBundleTypeRole
+ Viewer
+
+
+ CFBundleExecutable
+ droplet
+ CFBundleGetInfoString
+ DeDRM 5.4.1. AppleScript written 2010–2012 by Apprentice Alf and others.
+ CFBundleIconFile
+ DeDRM
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ DeDRM 5.4.1
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 5.4.1
+ CFBundleSignature
+ dplt
+ LSRequiresCarbon
+
+ WindowState
+
+ dividerCollapsed
+
+ eventLogLevel
+ -1
+ name
+ ScriptWindowState
+ positionOfDivider
+ 0
+ savedFrame
+ 287 405 800 473 0 0 1440 878
+ selectedTabView
+ event log
+
+
+
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/MacOS/droplet b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/MacOS/droplet
new file mode 100755
index 0000000..5436333
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/MacOS/droplet differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/PkgInfo b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/PkgInfo
new file mode 100644
index 0000000..b999e99
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPLdplt
\ No newline at end of file
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress Source.zip b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress Source.zip
new file mode 100644
index 0000000..d0daa2a
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress Source.zip differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist
new file mode 100644
index 0000000..6ca1088
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Info.plist
@@ -0,0 +1,50 @@
+
+
+
+
+ BuildMachineOSBuild
+ 10K549
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ DeDRM Progress
+ CFBundleGetInfoString
+ DeDRM Progress 1.1, Written 2010, 2012 by Apprentice Alf and others.
+ CFBundleIconFile
+ DeDRM Progress
+ CFBundleIdentifier
+ com.apprenticealf.DeDRMProgress
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ DeDRM Progress
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.1
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ DTCompiler
+ 4.0
+ DTPlatformBuild
+ 10M2518
+ DTPlatformVersion
+ PG
+ DTSDKBuild
+ 8S2167
+ DTSDKName
+ macosx10.4
+ DTXcode
+ 0400
+ DTXcodeBuild
+ 10M2518
+ NSAppleScriptEnabled
+ YES
+ NSMainNibFile
+ MainMenu
+ NSPrincipalClass
+ NSApplication
+
+
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress
new file mode 100755
index 0000000..ca47eb4
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/MacOS/DeDRM Progress differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL????
\ No newline at end of file
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/DeDRM Progress.icns b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/DeDRM Progress.icns
new file mode 100644
index 0000000..69855fc
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/DeDRM Progress.icns differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings
new file mode 100644
index 0000000..efaa0d7
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/InfoPlist.strings differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib
new file mode 100644
index 0000000..83ceb9e
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/English.lproj/MainMenu.nib differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt
new file mode 100644
index 0000000..5274a48
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM Progress.app/Contents/Resources/Scripts/Window.scpt differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM.icns b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM.icns
new file mode 100644
index 0000000..8a6dfe3
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/DeDRM.icns differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/Scripts/main.scpt b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/Scripts/main.scpt
new file mode 100644
index 0000000..14dc8b6
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/Scripts/main.scpt differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/aescbc.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/aescbc.py
new file mode 100755
index 0000000..5667511
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/aescbc.py
@@ -0,0 +1,568 @@
+#! /usr/bin/env python
+
+"""
+ Routines for doing AES CBC in one file
+
+ Modified by some_updates to extract
+ and combine only those parts needed for AES CBC
+ into one simple to add python file
+
+ Original Version
+ Copyright (c) 2002 by Paul A. Lambert
+ Under:
+ CryptoPy Artisitic License Version 1.0
+ See the wonderful pure python package cryptopy-1.2.5
+ and read its LICENSE.txt for complete license details.
+"""
+
+class CryptoError(Exception):
+ """ Base class for crypto exceptions """
+ def __init__(self,errorMessage='Error!'):
+ self.message = errorMessage
+ def __str__(self):
+ return self.message
+
+class InitCryptoError(CryptoError):
+ """ Crypto errors during algorithm initialization """
+class BadKeySizeError(InitCryptoError):
+ """ Bad key size error """
+class EncryptError(CryptoError):
+ """ Error in encryption processing """
+class DecryptError(CryptoError):
+ """ Error in decryption processing """
+class DecryptNotBlockAlignedError(DecryptError):
+ """ Error in decryption processing """
+
+def xorS(a,b):
+ """ XOR two strings """
+ assert len(a)==len(b)
+ x = []
+ for i in range(len(a)):
+ x.append( chr(ord(a[i])^ord(b[i])))
+ return ''.join(x)
+
+def xor(a,b):
+ """ XOR two strings """
+ x = []
+ for i in range(min(len(a),len(b))):
+ x.append( chr(ord(a[i])^ord(b[i])))
+ return ''.join(x)
+
+"""
+ Base 'BlockCipher' and Pad classes for cipher instances.
+ BlockCipher supports automatic padding and type conversion. The BlockCipher
+ class was written to make the actual algorithm code more readable and
+ not for performance.
+"""
+
+class BlockCipher:
+ """ Block ciphers """
+ def __init__(self):
+ self.reset()
+
+ def reset(self):
+ self.resetEncrypt()
+ self.resetDecrypt()
+ def resetEncrypt(self):
+ self.encryptBlockCount = 0
+ self.bytesToEncrypt = ''
+ def resetDecrypt(self):
+ self.decryptBlockCount = 0
+ self.bytesToDecrypt = ''
+
+ def encrypt(self, plainText, more = None):
+ """ Encrypt a string and return a binary string """
+ self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt
+ numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize)
+ cipherText = ''
+ for i in range(numBlocks):
+ bStart = i*self.blockSize
+ ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize])
+ self.encryptBlockCount += 1
+ cipherText += ctBlock
+ if numExtraBytes > 0: # save any bytes that are not block aligned
+ self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
+ else:
+ self.bytesToEncrypt = ''
+
+ if more == None: # no more data expected from caller
+ finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize)
+ if len(finalBytes) > 0:
+ ctBlock = self.encryptBlock(finalBytes)
+ self.encryptBlockCount += 1
+ cipherText += ctBlock
+ self.resetEncrypt()
+ return cipherText
+
+ def decrypt(self, cipherText, more = None):
+ """ Decrypt a string and return a string """
+ self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt
+
+ numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
+ if more == None: # no more calls to decrypt, should have all the data
+ if numExtraBytes != 0:
+ raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
+
+ # hold back some bytes in case last decrypt has zero len
+ if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
+ numBlocks -= 1
+ numExtraBytes = self.blockSize
+
+ plainText = ''
+ for i in range(numBlocks):
+ bStart = i*self.blockSize
+ ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
+ self.decryptBlockCount += 1
+ plainText += ptBlock
+
+ if numExtraBytes > 0: # save any bytes that are not block aligned
+ self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
+ else:
+ self.bytesToEncrypt = ''
+
+ if more == None: # last decrypt remove padding
+ plainText = self.padding.removePad(plainText, self.blockSize)
+ self.resetDecrypt()
+ return plainText
+
+
+class Pad:
+ def __init__(self):
+ pass # eventually could put in calculation of min and max size extension
+
+class padWithPadLen(Pad):
+ """ Pad a binary string with the length of the padding """
+
+ def addPad(self, extraBytes, blockSize):
+ """ Add padding to a binary string to make it an even multiple
+ of the block size """
+ blocks, numExtraBytes = divmod(len(extraBytes), blockSize)
+ padLength = blockSize - numExtraBytes
+ return extraBytes + padLength*chr(padLength)
+
+ def removePad(self, paddedBinaryString, blockSize):
+ """ Remove padding from a binary string """
+ if not(0 6 and i%Nk == 4 :
+ temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
+ w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
+ return w
+
+Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!!
+ 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,
+ 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91)
+
+#-------------------------------------
+def AddRoundKey(algInstance, keyBlock):
+ """ XOR the algorithm state with a block of key material """
+ for column in range(algInstance.Nb):
+ for row in range(4):
+ algInstance.state[column][row] ^= keyBlock[column][row]
+#-------------------------------------
+
+def SubBytes(algInstance):
+ for column in range(algInstance.Nb):
+ for row in range(4):
+ algInstance.state[column][row] = Sbox[algInstance.state[column][row]]
+
+def InvSubBytes(algInstance):
+ for column in range(algInstance.Nb):
+ for row in range(4):
+ algInstance.state[column][row] = InvSbox[algInstance.state[column][row]]
+
+Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,
+ 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
+ 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,
+ 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
+ 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,
+ 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
+ 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,
+ 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
+ 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,
+ 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
+ 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,
+ 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
+ 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,
+ 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
+ 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,
+ 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
+ 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,
+ 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
+ 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,
+ 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
+ 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,
+ 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
+ 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,
+ 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
+ 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,
+ 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
+ 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,
+ 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
+ 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,
+ 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
+ 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,
+ 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)
+
+InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,
+ 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
+ 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,
+ 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
+ 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,
+ 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
+ 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,
+ 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
+ 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,
+ 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
+ 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,
+ 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
+ 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,
+ 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
+ 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,
+ 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
+ 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,
+ 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
+ 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,
+ 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
+ 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,
+ 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
+ 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,
+ 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
+ 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,
+ 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
+ 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,
+ 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
+ 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,
+ 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
+ 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,
+ 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d)
+
+#-------------------------------------
+""" For each block size (Nb), the ShiftRow operation shifts row i
+ by the amount Ci. Note that row 0 is not shifted.
+ Nb C1 C2 C3
+ ------------------- """
+shiftOffset = { 4 : ( 0, 1, 2, 3),
+ 5 : ( 0, 1, 2, 3),
+ 6 : ( 0, 1, 2, 3),
+ 7 : ( 0, 1, 2, 4),
+ 8 : ( 0, 1, 3, 4) }
+def ShiftRows(algInstance):
+ tmp = [0]*algInstance.Nb # list of size Nb
+ for r in range(1,4): # row 0 reamains unchanged and can be skipped
+ for c in range(algInstance.Nb):
+ tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
+ for c in range(algInstance.Nb):
+ algInstance.state[c][r] = tmp[c]
+def InvShiftRows(algInstance):
+ tmp = [0]*algInstance.Nb # list of size Nb
+ for r in range(1,4): # row 0 reamains unchanged and can be skipped
+ for c in range(algInstance.Nb):
+ tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
+ for c in range(algInstance.Nb):
+ algInstance.state[c][r] = tmp[c]
+#-------------------------------------
+def MixColumns(a):
+ Sprime = [0,0,0,0]
+ for j in range(a.Nb): # for each column
+ Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3])
+ Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3])
+ Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3])
+ Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3])
+ for i in range(4):
+ a.state[j][i] = Sprime[i]
+
+def InvMixColumns(a):
+ """ Mix the four bytes of every column in a linear way
+ This is the opposite operation of Mixcolumn """
+ Sprime = [0,0,0,0]
+ for j in range(a.Nb): # for each column
+ Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3])
+ Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3])
+ Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3])
+ Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3])
+ for i in range(4):
+ a.state[j][i] = Sprime[i]
+
+#-------------------------------------
+def mul(a, b):
+ """ Multiply two elements of GF(2^m)
+ needed for MixColumn and InvMixColumn """
+ if (a !=0 and b!=0):
+ return Alogtable[(Logtable[a] + Logtable[b])%255]
+ else:
+ return 0
+
+Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
+ 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193,
+ 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120,
+ 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142,
+ 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56,
+ 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16,
+ 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186,
+ 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87,
+ 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232,
+ 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160,
+ 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183,
+ 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157,
+ 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209,
+ 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171,
+ 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165,
+ 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7)
+
+Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53,
+ 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170,
+ 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49,
+ 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205,
+ 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136,
+ 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154,
+ 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163,
+ 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160,
+ 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65,
+ 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117,
+ 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128,
+ 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84,
+ 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202,
+ 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14,
+ 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23,
+ 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1)
+
+
+
+
+"""
+ AES Encryption Algorithm
+ The AES algorithm is just Rijndael algorithm restricted to the default
+ blockSize of 128 bits.
+"""
+
+class AES(Rijndael):
+ """ The AES algorithm is the Rijndael block cipher restricted to block
+ sizes of 128 bits and key sizes of 128, 192 or 256 bits
+ """
+ def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
+ """ Initialize AES, keySize is in bytes """
+ if not (keySize == 16 or keySize == 24 or keySize == 32) :
+ raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
+
+ Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
+
+ self.name = 'AES'
+
+
+"""
+ CBC mode of encryption for block ciphers.
+ This algorithm mode wraps any BlockCipher to make a
+ Cipher Block Chaining mode.
+"""
+from random import Random # should change to crypto.random!!!
+
+
+class CBC(BlockCipher):
+ """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode
+ algorithms. The initialization (IV) is automatic if set to None. Padding
+ is also automatic based on the Pad class used to initialize the algorithm
+ """
+ def __init__(self, blockCipherInstance, padding = padWithPadLen()):
+ """ CBC algorithms are created by initializing with a BlockCipher instance """
+ self.baseCipher = blockCipherInstance
+ self.name = self.baseCipher.name + '_CBC'
+ self.blockSize = self.baseCipher.blockSize
+ self.keySize = self.baseCipher.keySize
+ self.padding = padding
+ self.baseCipher.padding = noPadding() # baseCipher should NOT pad!!
+ self.r = Random() # for IV generation, currently uses
+ # mediocre standard distro version <----------------
+ import time
+ newSeed = time.ctime()+str(self.r) # seed with instance location
+ self.r.seed(newSeed) # to make unique
+ self.reset()
+
+ def setKey(self, key):
+ self.baseCipher.setKey(key)
+
+ # Overload to reset both CBC state and the wrapped baseCipher
+ def resetEncrypt(self):
+ BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class)
+ self.baseCipher.resetEncrypt() # reset base cipher encrypt state
+
+ def resetDecrypt(self):
+ BlockCipher.resetDecrypt(self) # reset CBC state (super class)
+ self.baseCipher.resetDecrypt() # reset base cipher decrypt state
+
+ def encrypt(self, plainText, iv=None, more=None):
+ """ CBC encryption - overloads baseCipher to allow optional explicit IV
+ when iv=None, iv is auto generated!
+ """
+ if self.encryptBlockCount == 0:
+ self.iv = iv
+ else:
+ assert(iv==None), 'IV used only on first call to encrypt'
+
+ return BlockCipher.encrypt(self,plainText, more=more)
+
+ def decrypt(self, cipherText, iv=None, more=None):
+ """ CBC decryption - overloads baseCipher to allow optional explicit IV
+ when iv=None, iv is auto generated!
+ """
+ if self.decryptBlockCount == 0:
+ self.iv = iv
+ else:
+ assert(iv==None), 'IV used only on first call to decrypt'
+
+ return BlockCipher.decrypt(self, cipherText, more=more)
+
+ def encryptBlock(self, plainTextBlock):
+ """ CBC block encryption, IV is set with 'encrypt' """
+ auto_IV = ''
+ if self.encryptBlockCount == 0:
+ if self.iv == None:
+ # generate IV and use
+ self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)])
+ self.prior_encr_CT_block = self.iv
+ auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic
+ else: # application provided IV
+ assert(len(self.iv) == self.blockSize ),'IV must be same length as block'
+ self.prior_encr_CT_block = self.iv
+ """ encrypt the prior CT XORed with the PT """
+ ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) )
+ self.prior_encr_CT_block = ct
+ return auto_IV+ct
+
+ def decryptBlock(self, encryptedBlock):
+ """ Decrypt a single block """
+
+ if self.decryptBlockCount == 0: # first call, process IV
+ if self.iv == None: # auto decrypt IV?
+ self.prior_CT_block = encryptedBlock
+ return ''
+ else:
+ assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
+ self.prior_CT_block = self.iv
+
+ dct = self.baseCipher.decryptBlock(encryptedBlock)
+ """ XOR the prior decrypted CT with the prior CT """
+ dct_XOR_priorCT = xor( self.prior_CT_block, dct )
+
+ self.prior_CT_block = encryptedBlock
+
+ return dct_XOR_priorCT
+
+
+"""
+ AES_CBC Encryption Algorithm
+"""
+
+class AES_CBC(CBC):
+ """ AES encryption in CBC feedback mode """
+ def __init__(self, key=None, padding=padWithPadLen(), keySize=16):
+ CBC.__init__( self, AES(key, noPadding(), keySize), padding)
+ self.name = 'AES_CBC'
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.dll b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.dll
new file mode 100755
index 0000000..26d740d
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.dll differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.py
new file mode 100755
index 0000000..e25a0c8
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto.py
@@ -0,0 +1,290 @@
+#! /usr/bin/env python
+
+import sys, os
+import hmac
+from struct import pack
+import hashlib
+
+
+# interface to needed routines libalfcrypto
+def _load_libalfcrypto():
+ import ctypes
+ from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
+ Structure, c_ulong, create_string_buffer, addressof, string_at, cast, sizeof
+
+ pointer_size = ctypes.sizeof(ctypes.c_voidp)
+ name_of_lib = None
+ if sys.platform.startswith('darwin'):
+ name_of_lib = 'libalfcrypto.dylib'
+ elif sys.platform.startswith('win'):
+ if pointer_size == 4:
+ name_of_lib = 'alfcrypto.dll'
+ else:
+ name_of_lib = 'alfcrypto64.dll'
+ else:
+ if pointer_size == 4:
+ name_of_lib = 'libalfcrypto32.so'
+ else:
+ name_of_lib = 'libalfcrypto64.so'
+
+ libalfcrypto = sys.path[0] + os.sep + name_of_lib
+
+ if not os.path.isfile(libalfcrypto):
+ raise Exception('libalfcrypto not found')
+
+ libalfcrypto = CDLL(libalfcrypto)
+
+ c_char_pp = POINTER(c_char_p)
+ c_int_p = POINTER(c_int)
+
+
+ def F(restype, name, argtypes):
+ func = getattr(libalfcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ # aes cbc decryption
+ #
+ # struct aes_key_st {
+ # unsigned long rd_key[4 *(AES_MAXNR + 1)];
+ # int rounds;
+ # };
+ #
+ # typedef struct aes_key_st AES_KEY;
+ #
+ # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
+ #
+ #
+ # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
+ # const unsigned long length, const AES_KEY *key,
+ # unsigned char *ivec, const int enc);
+
+ AES_MAXNR = 14
+
+ class AES_KEY(Structure):
+ _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
+
+ AES_KEY_p = POINTER(AES_KEY)
+ AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int])
+ AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
+
+
+
+ # Pukall 1 Cipher
+ # unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src,
+ # unsigned char *dest, unsigned int len, int decryption);
+
+ PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong])
+
+ # Topaz Encryption
+ # typedef struct _TpzCtx {
+ # unsigned int v[2];
+ # } TpzCtx;
+ #
+ # void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen);
+ # void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len);
+
+ class TPZ_CTX(Structure):
+ _fields_ = [('v', c_long * 2)]
+
+ TPZ_CTX_p = POINTER(TPZ_CTX)
+ topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong])
+ topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong])
+
+
+ class AES_CBC(object):
+ def __init__(self):
+ self._blocksize = 0
+ self._keyctx = None
+ self._iv = 0
+
+ def set_decrypt_key(self, userkey, iv):
+ self._blocksize = len(userkey)
+ if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+ raise Exception('AES CBC improper key used')
+ return
+ keyctx = self._keyctx = AES_KEY()
+ self._iv = iv
+ rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
+ if rv < 0:
+ raise Exception('Failed to initialize AES CBC key')
+
+ def decrypt(self, data):
+ out = create_string_buffer(len(data))
+ mutable_iv = create_string_buffer(self._iv, len(self._iv))
+ rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0)
+ if rv == 0:
+ raise Exception('AES CBC decryption failed')
+ return out.raw
+
+ class Pukall_Cipher(object):
+ def __init__(self):
+ self.key = None
+
+ def PC1(self, key, src, decryption=True):
+ self.key = key
+ out = create_string_buffer(len(src))
+ de = 0
+ if decryption:
+ de = 1
+ rv = PC1(key, len(key), src, out, len(src), de)
+ return out.raw
+
+ class Topaz_Cipher(object):
+ def __init__(self):
+ self._ctx = None
+
+ def ctx_init(self, key):
+ tpz_ctx = self._ctx = TPZ_CTX()
+ topazCryptoInit(tpz_ctx, key, len(key))
+ return tpz_ctx
+
+ def decrypt(self, data, ctx=None):
+ if ctx == None:
+ ctx = self._ctx
+ out = create_string_buffer(len(data))
+ topazCryptoDecrypt(ctx, data, out, len(data))
+ return out.raw
+
+ print "Using Library AlfCrypto DLL/DYLIB/SO"
+ return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
+
+
+def _load_python_alfcrypto():
+
+ import aescbc
+
+ class Pukall_Cipher(object):
+ def __init__(self):
+ self.key = None
+
+ def PC1(self, key, src, decryption=True):
+ sum1 = 0;
+ sum2 = 0;
+ keyXorVal = 0;
+ if len(key)!=16:
+ print "Bad key length!"
+ return None
+ wkey = []
+ for i in xrange(8):
+ wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
+ dst = ""
+ for i in xrange(len(src)):
+ temp1 = 0;
+ byteXorVal = 0;
+ for j in xrange(8):
+ temp1 ^= wkey[j]
+ sum2 = (sum2+j)*20021 + sum1
+ sum1 = (temp1*346)&0xFFFF
+ sum2 = (sum2+sum1)&0xFFFF
+ temp1 = (temp1*20021+1)&0xFFFF
+ byteXorVal ^= temp1 ^ sum2
+ curByte = ord(src[i])
+ if not decryption:
+ keyXorVal = curByte * 257;
+ curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
+ if decryption:
+ keyXorVal = curByte * 257;
+ for j in xrange(8):
+ wkey[j] ^= keyXorVal;
+ dst+=chr(curByte)
+ return dst
+
+ class Topaz_Cipher(object):
+ def __init__(self):
+ self._ctx = None
+
+ def ctx_init(self, key):
+ ctx1 = 0x0CAFFE19E
+ for keyChar in key:
+ keyByte = ord(keyChar)
+ ctx2 = ctx1
+ ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF )
+ self._ctx = [ctx1, ctx2]
+ return [ctx1,ctx2]
+
+ def decrypt(self, data, ctx=None):
+ if ctx == None:
+ ctx = self._ctx
+ ctx1 = ctx[0]
+ ctx2 = ctx[1]
+ plainText = ""
+ for dataChar in data:
+ dataByte = ord(dataChar)
+ m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF
+ ctx2 = ctx1
+ ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF)
+ plainText += chr(m)
+ return plainText
+
+ class AES_CBC(object):
+ def __init__(self):
+ self._key = None
+ self._iv = None
+ self.aes = None
+
+ def set_decrypt_key(self, userkey, iv):
+ self._key = userkey
+ self._iv = iv
+ self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey))
+
+ def decrypt(self, data):
+ iv = self._iv
+ cleartext = self.aes.decrypt(iv + data)
+ return cleartext
+
+ return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
+
+
+def _load_crypto():
+ AES_CBC = Pukall_Cipher = Topaz_Cipher = None
+ cryptolist = (_load_libalfcrypto, _load_python_alfcrypto)
+ for loader in cryptolist:
+ try:
+ AES_CBC, Pukall_Cipher, Topaz_Cipher = loader()
+ break
+ except (ImportError, Exception):
+ pass
+ return AES_CBC, Pukall_Cipher, Topaz_Cipher
+
+AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto()
+
+
+class KeyIVGen(object):
+ # this only exists in openssl so we will use pure python implementation instead
+ # PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
+ # [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
+ def pbkdf2(self, passwd, salt, iter, keylen):
+
+ def xorstr( a, b ):
+ if len(a) != len(b):
+ raise Exception("xorstr(): lengths differ")
+ return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
+
+ def prf( h, data ):
+ hm = h.copy()
+ hm.update( data )
+ return hm.digest()
+
+ def pbkdf2_F( h, salt, itercount, blocknum ):
+ U = prf( h, salt + pack('>i',blocknum ) )
+ T = U
+ for i in range(2, itercount+1):
+ U = prf( h, U )
+ T = xorstr( T, U )
+ return T
+
+ sha = hashlib.sha1
+ digest_size = sha().digest_size
+ # l - number of output blocks to produce
+ l = keylen / digest_size
+ if keylen % digest_size != 0:
+ l += 1
+ h = hmac.new( passwd, None, sha )
+ T = ""
+ for i in range(1, l+1):
+ T += pbkdf2_F( h, salt, iter, i )
+ return T[0: keylen]
+
+
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto64.dll b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto64.dll
new file mode 100755
index 0000000..7bef68e
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto64.dll differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto_src.zip b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto_src.zip
new file mode 100755
index 0000000..269810c
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/alfcrypto_src.zip differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/config.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/config.py
new file mode 100644
index 0000000..9825878
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/config.py
@@ -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('eInk Kindle Serial numbers (First character B, 16 characters, use commas if more than one)')
+ self.l.addWidget(self.serialLabel)
+
+ self.serials = QLineEdit(self)
+ self.serials.setText(prefs['serials'])
+ self.l.addWidget(self.serials)
+ self.serialLabel.setBuddy(self.serials)
+
+ self.pidLabel = QLabel('Mobipocket PIDs (8 or 10 characters, use commas if more than one)')
+ self.l.addWidget(self.pidLabel)
+
+ self.pids = QLineEdit(self)
+ self.pids.setText(prefs['pids'])
+ self.l.addWidget(self.pids)
+ self.pidLabel.setBuddy(self.serials)
+
+ self.wpLabel = QLabel('For Linux only: WINEPREFIX (enter absolute path)')
+ self.l.addWidget(self.wpLabel)
+
+ self.wineprefix = QLineEdit(self)
+ wineprefix = prefs['WINEPREFIX']
+ if wineprefix is not None:
+ self.wineprefix.setText(wineprefix)
+ else:
+ self.wineprefix.setText('')
+
+ self.l.addWidget(self.wineprefix)
+ self.wpLabel.setBuddy(self.wineprefix)
+
+ def save_settings(self):
+ prefs['pids'] = str(self.pids.text()).replace(" ","")
+ prefs['serials'] = str(self.serials.text()).replace(" ","")
+ winepref=str(self.wineprefix.text())
+ if winepref.strip() != '':
+ prefs['WINEPREFIX'] = winepref
+ else:
+ prefs['WINEPREFIX'] = None
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/convert2xml.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/convert2xml.py
new file mode 100755
index 0000000..c412d7b
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/convert2xml.py
@@ -0,0 +1,846 @@
+#! /usr/bin/python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+# For use with Topaz Scripts Version 2.6
+
+class Unbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ def write(self, data):
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+import sys
+sys.stdout=Unbuffered(sys.stdout)
+
+import csv
+import os
+import getopt
+from struct import pack
+from struct import unpack
+
+class TpzDRMError(Exception):
+ pass
+
+# Get a 7 bit encoded number from string. The most
+# significant byte comes first and has the high bit (8th) set
+
+def readEncodedNumber(file):
+ flag = False
+ c = file.read(1)
+ if (len(c) == 0):
+ return None
+ data = ord(c)
+
+ if data == 0xFF:
+ flag = True
+ c = file.read(1)
+ if (len(c) == 0):
+ return None
+ data = ord(c)
+
+ if data >= 0x80:
+ datax = (data & 0x7F)
+ while data >= 0x80 :
+ c = file.read(1)
+ if (len(c) == 0):
+ return None
+ data = ord(c)
+ datax = (datax <<7) + (data & 0x7F)
+ data = datax
+
+ if flag:
+ data = -data
+ return data
+
+
+# returns a binary string that encodes a number into 7 bits
+# most significant byte first which has the high bit set
+
+def encodeNumber(number):
+ result = ""
+ negative = False
+ flag = 0
+
+ if number < 0 :
+ number = -number + 1
+ negative = True
+
+ while True:
+ byte = number & 0x7F
+ number = number >> 7
+ byte += flag
+ result += chr(byte)
+ flag = 0x80
+ if number == 0 :
+ if (byte == 0xFF and negative == False) :
+ result += chr(0x80)
+ break
+
+ if negative:
+ result += chr(0xFF)
+
+ return result[::-1]
+
+
+
+# create / read a length prefixed string from the file
+
+def lengthPrefixString(data):
+ return encodeNumber(len(data))+data
+
+def readString(file):
+ stringLength = readEncodedNumber(file)
+ if (stringLength == None):
+ return ""
+ sv = file.read(stringLength)
+ if (len(sv) != stringLength):
+ return ""
+ return unpack(str(stringLength)+"s",sv)[0]
+
+
+# convert a binary string generated by encodeNumber (7 bit encoded number)
+# to the value you would find inside the page*.dat files to be processed
+
+def convert(i):
+ result = ''
+ val = encodeNumber(i)
+ for j in xrange(len(val)):
+ c = ord(val[j:j+1])
+ result += '%02x' % c
+ return result
+
+
+
+# the complete string table used to store all book text content
+# as well as the xml tokens and values that make sense out of it
+
+class Dictionary(object):
+ def __init__(self, dictFile):
+ self.filename = dictFile
+ self.size = 0
+ self.fo = file(dictFile,'rb')
+ self.stable = []
+ self.size = readEncodedNumber(self.fo)
+ for i in xrange(self.size):
+ self.stable.append(self.escapestr(readString(self.fo)))
+ self.pos = 0
+
+ def escapestr(self, str):
+ str = str.replace('&','&')
+ str = str.replace('<','<')
+ str = str.replace('>','>')
+ str = str.replace('=','=')
+ return str
+
+ def lookup(self,val):
+ if ((val >= 0) and (val < self.size)) :
+ self.pos = val
+ return self.stable[self.pos]
+ else:
+ print "Error - %d outside of string table limits" % val
+ raise TpzDRMError('outside of string table limits')
+ # sys.exit(-1)
+
+ def getSize(self):
+ return self.size
+
+ def getPos(self):
+ return self.pos
+
+ def dumpDict(self):
+ for i in xrange(self.size):
+ print "%d %s %s" % (i, convert(i), self.stable[i])
+ return
+
+# parses the xml snippets that are represented by each page*.dat file.
+# also parses the other0.dat file - the main stylesheet
+# and information used to inject the xml snippets into page*.dat files
+
+class PageParser(object):
+ def __init__(self, filename, dict, debug, flat_xml):
+ self.fo = file(filename,'rb')
+ self.id = os.path.basename(filename).replace('.dat','')
+ self.dict = dict
+ self.debug = debug
+ self.flat_xml = flat_xml
+ self.tagpath = []
+ self.doc = []
+ self.snippetList = []
+
+
+ # hash table used to enable the decoding process
+ # This has all been developed by trial and error so it may still have omissions or
+ # contain errors
+ # Format:
+ # tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped)
+
+ token_tags = {
+ 'x' : (1, 'scalar_number', 0, 0),
+ 'y' : (1, 'scalar_number', 0, 0),
+ 'h' : (1, 'scalar_number', 0, 0),
+ 'w' : (1, 'scalar_number', 0, 0),
+ 'firstWord' : (1, 'scalar_number', 0, 0),
+ 'lastWord' : (1, 'scalar_number', 0, 0),
+ 'rootID' : (1, 'scalar_number', 0, 0),
+ 'stemID' : (1, 'scalar_number', 0, 0),
+ 'type' : (1, 'scalar_text', 0, 0),
+
+ 'info' : (0, 'number', 1, 0),
+
+ 'info.word' : (0, 'number', 1, 1),
+ 'info.word.ocrText' : (1, 'text', 0, 0),
+ 'info.word.firstGlyph' : (1, 'raw', 0, 0),
+ 'info.word.lastGlyph' : (1, 'raw', 0, 0),
+ 'info.word.bl' : (1, 'raw', 0, 0),
+ 'info.word.link_id' : (1, 'number', 0, 0),
+
+ 'glyph' : (0, 'number', 1, 1),
+ 'glyph.x' : (1, 'number', 0, 0),
+ 'glyph.y' : (1, 'number', 0, 0),
+ 'glyph.glyphID' : (1, 'number', 0, 0),
+
+ 'dehyphen' : (0, 'number', 1, 1),
+ 'dehyphen.rootID' : (1, 'number', 0, 0),
+ 'dehyphen.stemID' : (1, 'number', 0, 0),
+ 'dehyphen.stemPage' : (1, 'number', 0, 0),
+ 'dehyphen.sh' : (1, 'number', 0, 0),
+
+ 'links' : (0, 'number', 1, 1),
+ 'links.page' : (1, 'number', 0, 0),
+ 'links.rel' : (1, 'number', 0, 0),
+ 'links.row' : (1, 'number', 0, 0),
+ 'links.title' : (1, 'text', 0, 0),
+ 'links.href' : (1, 'text', 0, 0),
+ 'links.type' : (1, 'text', 0, 0),
+ 'links.id' : (1, 'number', 0, 0),
+
+ 'paraCont' : (0, 'number', 1, 1),
+ 'paraCont.rootID' : (1, 'number', 0, 0),
+ 'paraCont.stemID' : (1, 'number', 0, 0),
+ 'paraCont.stemPage' : (1, 'number', 0, 0),
+
+ 'paraStems' : (0, 'number', 1, 1),
+ 'paraStems.stemID' : (1, 'number', 0, 0),
+
+ 'wordStems' : (0, 'number', 1, 1),
+ 'wordStems.stemID' : (1, 'number', 0, 0),
+
+ 'empty' : (1, 'snippets', 1, 0),
+
+ 'page' : (1, 'snippets', 1, 0),
+ 'page.pageid' : (1, 'scalar_text', 0, 0),
+ 'page.pagelabel' : (1, 'scalar_text', 0, 0),
+ 'page.type' : (1, 'scalar_text', 0, 0),
+ 'page.h' : (1, 'scalar_number', 0, 0),
+ 'page.w' : (1, 'scalar_number', 0, 0),
+ 'page.startID' : (1, 'scalar_number', 0, 0),
+
+ 'group' : (1, 'snippets', 1, 0),
+ 'group.type' : (1, 'scalar_text', 0, 0),
+ 'group._tag' : (1, 'scalar_text', 0, 0),
+ 'group.orientation': (1, 'scalar_text', 0, 0),
+
+ 'region' : (1, 'snippets', 1, 0),
+ 'region.type' : (1, 'scalar_text', 0, 0),
+ 'region.x' : (1, 'scalar_number', 0, 0),
+ 'region.y' : (1, 'scalar_number', 0, 0),
+ 'region.h' : (1, 'scalar_number', 0, 0),
+ 'region.w' : (1, 'scalar_number', 0, 0),
+ 'region.orientation' : (1, 'scalar_text', 0, 0),
+
+ 'empty_text_region' : (1, 'snippets', 1, 0),
+
+ 'img' : (1, 'snippets', 1, 0),
+ 'img.x' : (1, 'scalar_number', 0, 0),
+ 'img.y' : (1, 'scalar_number', 0, 0),
+ 'img.h' : (1, 'scalar_number', 0, 0),
+ 'img.w' : (1, 'scalar_number', 0, 0),
+ 'img.src' : (1, 'scalar_number', 0, 0),
+ 'img.color_src' : (1, 'scalar_number', 0, 0),
+
+ 'paragraph' : (1, 'snippets', 1, 0),
+ 'paragraph.class' : (1, 'scalar_text', 0, 0),
+ 'paragraph.firstWord' : (1, 'scalar_number', 0, 0),
+ 'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
+ 'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
+ 'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
+ 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
+ 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
+ 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
+ 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
+
+
+ 'word_semantic' : (1, 'snippets', 1, 1),
+ 'word_semantic.type' : (1, 'scalar_text', 0, 0),
+ 'word_semantic.firstWord' : (1, 'scalar_number', 0, 0),
+ 'word_semantic.lastWord' : (1, 'scalar_number', 0, 0),
+
+ 'word' : (1, 'snippets', 1, 0),
+ 'word.type' : (1, 'scalar_text', 0, 0),
+ 'word.class' : (1, 'scalar_text', 0, 0),
+ 'word.firstGlyph' : (1, 'scalar_number', 0, 0),
+ 'word.lastGlyph' : (1, 'scalar_number', 0, 0),
+
+ '_span' : (1, 'snippets', 1, 0),
+ '_span.firstWord' : (1, 'scalar_number', 0, 0),
+ '_span.lastWord' : (1, 'scalar_number', 0, 0),
+ '_span.gridSize' : (1, 'scalar_number', 0, 0),
+ '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
+ '_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
+ '_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
+ '_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
+
+ 'span' : (1, 'snippets', 1, 0),
+ 'span.firstWord' : (1, 'scalar_number', 0, 0),
+ 'span.lastWord' : (1, 'scalar_number', 0, 0),
+ 'span.gridSize' : (1, 'scalar_number', 0, 0),
+ 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
+ 'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
+ 'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
+ 'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
+
+ 'extratokens' : (1, 'snippets', 1, 0),
+ 'extratokens.type' : (1, 'scalar_text', 0, 0),
+ 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
+ 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),
+
+ 'glyph.h' : (1, 'number', 0, 0),
+ 'glyph.w' : (1, 'number', 0, 0),
+ 'glyph.use' : (1, 'number', 0, 0),
+ 'glyph.vtx' : (1, 'number', 0, 1),
+ 'glyph.len' : (1, 'number', 0, 1),
+ 'glyph.dpi' : (1, 'number', 0, 0),
+ 'vtx' : (0, 'number', 1, 1),
+ 'vtx.x' : (1, 'number', 0, 0),
+ 'vtx.y' : (1, 'number', 0, 0),
+ 'len' : (0, 'number', 1, 1),
+ 'len.n' : (1, 'number', 0, 0),
+
+ 'book' : (1, 'snippets', 1, 0),
+ 'version' : (1, 'snippets', 1, 0),
+ 'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0),
+ 'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0),
+ 'version.Schema_id' : (1, 'scalar_text', 0, 0),
+ 'version.Schema_version' : (1, 'scalar_text', 0, 0),
+ 'version.Topaz_version' : (1, 'scalar_text', 0, 0),
+ 'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0),
+ 'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0),
+ 'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0),
+ 'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0),
+ 'version.chapterheaders' : (1, 'scalar_text', 0, 0),
+ 'version.creation_date' : (1, 'scalar_text', 0, 0),
+ 'version.header_footer' : (1, 'scalar_text', 0, 0),
+ 'version.init_from_ocr' : (1, 'scalar_text', 0, 0),
+ 'version.letter_insertion' : (1, 'scalar_text', 0, 0),
+ 'version.xmlinj_convert' : (1, 'scalar_text', 0, 0),
+ 'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0),
+ 'version.xmlinj_transform' : (1, 'scalar_text', 0, 0),
+ 'version.findlists' : (1, 'scalar_text', 0, 0),
+ 'version.page_num' : (1, 'scalar_text', 0, 0),
+ 'version.page_type' : (1, 'scalar_text', 0, 0),
+ 'version.bad_text' : (1, 'scalar_text', 0, 0),
+ 'version.glyph_mismatch' : (1, 'scalar_text', 0, 0),
+ 'version.margins' : (1, 'scalar_text', 0, 0),
+ 'version.staggered_lines' : (1, 'scalar_text', 0, 0),
+ 'version.paragraph_continuation' : (1, 'scalar_text', 0, 0),
+ 'version.toc' : (1, 'scalar_text', 0, 0),
+
+ 'stylesheet' : (1, 'snippets', 1, 0),
+ 'style' : (1, 'snippets', 1, 0),
+ 'style._tag' : (1, 'scalar_text', 0, 0),
+ 'style.type' : (1, 'scalar_text', 0, 0),
+ 'style._parent_type' : (1, 'scalar_text', 0, 0),
+ 'style.class' : (1, 'scalar_text', 0, 0),
+ 'style._after_class' : (1, 'scalar_text', 0, 0),
+ 'rule' : (1, 'snippets', 1, 0),
+ 'rule.attr' : (1, 'scalar_text', 0, 0),
+ 'rule.value' : (1, 'scalar_text', 0, 0),
+
+ 'original' : (0, 'number', 1, 1),
+ 'original.pnum' : (1, 'number', 0, 0),
+ 'original.pid' : (1, 'text', 0, 0),
+ 'pages' : (0, 'number', 1, 1),
+ 'pages.ref' : (1, 'number', 0, 0),
+ 'pages.id' : (1, 'number', 0, 0),
+ 'startID' : (0, 'number', 1, 1),
+ 'startID.page' : (1, 'number', 0, 0),
+ 'startID.id' : (1, 'number', 0, 0),
+
+ }
+
+
+ # full tag path record keeping routines
+ def tag_push(self, token):
+ self.tagpath.append(token)
+ def tag_pop(self):
+ if len(self.tagpath) > 0 :
+ self.tagpath.pop()
+ def tagpath_len(self):
+ return len(self.tagpath)
+ def get_tagpath(self, i):
+ cnt = len(self.tagpath)
+ if i < cnt : result = self.tagpath[i]
+ for j in xrange(i+1, cnt) :
+ result += '.' + self.tagpath[j]
+ return result
+
+
+ # list of absolute command byte values values that indicate
+ # various types of loop meachanisms typically used to generate vectors
+
+ cmd_list = (0x76, 0x76)
+
+ # peek at and return 1 byte that is ahead by i bytes
+ def peek(self, aheadi):
+ c = self.fo.read(aheadi)
+ if (len(c) == 0):
+ return None
+ self.fo.seek(-aheadi,1)
+ c = c[-1:]
+ return ord(c)
+
+
+ # get the next value from the file being processed
+ def getNext(self):
+ nbyte = self.peek(1);
+ if (nbyte == None):
+ return None
+ val = readEncodedNumber(self.fo)
+ return val
+
+
+ # format an arg by argtype
+ def formatArg(self, arg, argtype):
+ if (argtype == 'text') or (argtype == 'scalar_text') :
+ result = self.dict.lookup(arg)
+ elif (argtype == 'raw') or (argtype == 'number') or (argtype == 'scalar_number') :
+ result = arg
+ elif (argtype == 'snippets') :
+ result = arg
+ else :
+ print "Error Unknown argtype %s" % argtype
+ sys.exit(-2)
+ return result
+
+
+ # process the next tag token, recursively handling subtags,
+ # arguments, and commands
+ def procToken(self, token):
+
+ known_token = False
+ self.tag_push(token)
+
+ if self.debug : print 'Processing: ', self.get_tagpath(0)
+ cnt = self.tagpath_len()
+ for j in xrange(cnt):
+ tkn = self.get_tagpath(j)
+ if tkn in self.token_tags :
+ num_args = self.token_tags[tkn][0]
+ argtype = self.token_tags[tkn][1]
+ subtags = self.token_tags[tkn][2]
+ splcase = self.token_tags[tkn][3]
+ ntags = -1
+ known_token = True
+ break
+
+ if known_token :
+
+ # handle subtags if present
+ subtagres = []
+ if (splcase == 1):
+ # this type of tag uses of escape marker 0x74 indicate subtag count
+ if self.peek(1) == 0x74:
+ skip = readEncodedNumber(self.fo)
+ subtags = 1
+ num_args = 0
+
+ if (subtags == 1):
+ ntags = readEncodedNumber(self.fo)
+ if self.debug : print 'subtags: ' + token + ' has ' + str(ntags)
+ for j in xrange(ntags):
+ val = readEncodedNumber(self.fo)
+ subtagres.append(self.procToken(self.dict.lookup(val)))
+
+ # arguments can be scalars or vectors of text or numbers
+ argres = []
+ if num_args > 0 :
+ firstarg = self.peek(1)
+ if (firstarg in self.cmd_list) and (argtype != 'scalar_number') and (argtype != 'scalar_text'):
+ # single argument is a variable length vector of data
+ arg = readEncodedNumber(self.fo)
+ argres = self.decodeCMD(arg,argtype)
+ else :
+ # num_arg scalar arguments
+ for i in xrange(num_args):
+ argres.append(self.formatArg(readEncodedNumber(self.fo), argtype))
+
+ # build the return tag
+ result = []
+ tkn = self.get_tagpath(0)
+ result.append(tkn)
+ result.append(subtagres)
+ result.append(argtype)
+ result.append(argres)
+ self.tag_pop()
+ return result
+
+ # all tokens that need to be processed should be in the hash
+ # table if it may indicate a problem, either new token
+ # or an out of sync condition
+ else:
+ result = []
+ if (self.debug):
+ print 'Unknown Token:', token
+ self.tag_pop()
+ return result
+
+
+ # special loop used to process code snippets
+ # it is NEVER used to format arguments.
+ # builds the snippetList
+ def doLoop72(self, argtype):
+ cnt = readEncodedNumber(self.fo)
+ if self.debug :
+ result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n'
+ result += 'of the document is indicated by snippet number sets at the\n'
+ result += 'end of each snippet. \n'
+ print result
+ for i in xrange(cnt):
+ if self.debug: print 'Snippet:',str(i)
+ snippet = []
+ snippet.append(i)
+ val = readEncodedNumber(self.fo)
+ snippet.append(self.procToken(self.dict.lookup(val)))
+ self.snippetList.append(snippet)
+ return
+
+
+
+ # general loop code gracisouly submitted by "skindle" - thank you!
+ def doLoop76Mode(self, argtype, cnt, mode):
+ result = []
+ adj = 0
+ if mode & 1:
+ adj = readEncodedNumber(self.fo)
+ mode = mode >> 1
+ x = []
+ for i in xrange(cnt):
+ x.append(readEncodedNumber(self.fo) - adj)
+ for i in xrange(mode):
+ for j in xrange(1, cnt):
+ x[j] = x[j] + x[j - 1]
+ for i in xrange(cnt):
+ result.append(self.formatArg(x[i],argtype))
+ return result
+
+
+ # dispatches loop commands bytes with various modes
+ # The 0x76 style loops are used to build vectors
+
+ # This was all derived by trial and error and
+ # new loop types may exist that are not handled here
+ # since they did not appear in the test cases
+
+ def decodeCMD(self, cmd, argtype):
+ if (cmd == 0x76):
+
+ # loop with cnt, and mode to control loop styles
+ cnt = readEncodedNumber(self.fo)
+ mode = readEncodedNumber(self.fo)
+
+ if self.debug : print 'Loop for', cnt, 'with mode', mode, ': '
+ return self.doLoop76Mode(argtype, cnt, mode)
+
+ if self.dbug: print "Unknown command", cmd
+ result = []
+ return result
+
+
+
+ # add full tag path to injected snippets
+ def updateName(self, tag, prefix):
+ name = tag[0]
+ subtagList = tag[1]
+ argtype = tag[2]
+ argList = tag[3]
+ nname = prefix + '.' + name
+ nsubtaglist = []
+ for j in subtagList:
+ nsubtaglist.append(self.updateName(j,prefix))
+ ntag = []
+ ntag.append(nname)
+ ntag.append(nsubtaglist)
+ ntag.append(argtype)
+ ntag.append(argList)
+ return ntag
+
+
+
+ # perform depth first injection of specified snippets into this one
+ def injectSnippets(self, snippet):
+ snipno, tag = snippet
+ name = tag[0]
+ subtagList = tag[1]
+ argtype = tag[2]
+ argList = tag[3]
+ nsubtagList = []
+ if len(argList) > 0 :
+ for j in argList:
+ asnip = self.snippetList[j]
+ aso, atag = self.injectSnippets(asnip)
+ atag = self.updateName(atag, name)
+ nsubtagList.append(atag)
+ argtype='number'
+ argList=[]
+ if len(nsubtagList) > 0 :
+ subtagList.extend(nsubtagList)
+ tag = []
+ tag.append(name)
+ tag.append(subtagList)
+ tag.append(argtype)
+ tag.append(argList)
+ snippet = []
+ snippet.append(snipno)
+ snippet.append(tag)
+ return snippet
+
+
+
+ # format the tag for output
+ def formatTag(self, node):
+ name = node[0]
+ subtagList = node[1]
+ argtype = node[2]
+ argList = node[3]
+ fullpathname = name.split('.')
+ nodename = fullpathname.pop()
+ ilvl = len(fullpathname)
+ indent = ' ' * (3 * ilvl)
+ rlst = []
+ rlst.append(indent + '<' + nodename + '>')
+ if len(argList) > 0:
+ alst = []
+ for j in argList:
+ if (argtype == 'text') or (argtype == 'scalar_text') :
+ alst.append(j + '|')
+ else :
+ alst.append(str(j) + ',')
+ argres = "".join(alst)
+ argres = argres[0:-1]
+ if argtype == 'snippets' :
+ rlst.append('snippets:' + argres)
+ else :
+ rlst.append(argres)
+ if len(subtagList) > 0 :
+ rlst.append('\n')
+ for j in subtagList:
+ if len(j) > 0 :
+ rlst.append(self.formatTag(j))
+ rlst.append(indent + '' + nodename + '>\n')
+ else:
+ rlst.append('' + nodename + '>\n')
+ return "".join(rlst)
+
+
+ # flatten tag
+ def flattenTag(self, node):
+ name = node[0]
+ subtagList = node[1]
+ argtype = node[2]
+ argList = node[3]
+ rlst = []
+ rlst.append(name)
+ if (len(argList) > 0):
+ alst = []
+ for j in argList:
+ if (argtype == 'text') or (argtype == 'scalar_text') :
+ alst.append(j + '|')
+ else :
+ alst.append(str(j) + '|')
+ argres = "".join(alst)
+ argres = argres[0:-1]
+ if argtype == 'snippets' :
+ rlst.append('.snippets=' + argres)
+ else :
+ rlst.append('=' + argres)
+ rlst.append('\n')
+ for j in subtagList:
+ if len(j) > 0 :
+ rlst.append(self.flattenTag(j))
+ return "".join(rlst)
+
+
+ # reduce create xml output
+ def formatDoc(self, flat_xml):
+ rlst = []
+ for j in self.doc :
+ if len(j) > 0:
+ if flat_xml:
+ rlst.append(self.flattenTag(j))
+ else:
+ rlst.append(self.formatTag(j))
+ result = "".join(rlst)
+ if self.debug : print result
+ return result
+
+
+
+ # main loop - parse the page.dat files
+ # to create structured document and snippets
+
+ # FIXME: value at end of magic appears to be a subtags count
+ # but for what? For now, inject an 'info" tag as it is in
+ # every dictionary and seems close to what is meant
+ # The alternative is to special case the last _ "0x5f" to mean something
+
+ def process(self):
+
+ # peek at the first bytes to see what type of file it is
+ magic = self.fo.read(9)
+ if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'):
+ first_token = 'info'
+ elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'):
+ skip = self.fo.read(2)
+ first_token = 'info'
+ elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'):
+ first_token = 'info'
+ elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'):
+ skip = self.fo.read(3)
+ first_token = 'info'
+ else :
+ # other0.dat file
+ first_token = None
+ self.fo.seek(-9,1)
+
+
+ # main loop to read and build the document tree
+ while True:
+
+ if first_token != None :
+ # use "inserted" first token 'info' for page and glyph files
+ tag = self.procToken(first_token)
+ if len(tag) > 0 :
+ self.doc.append(tag)
+ first_token = None
+
+ v = self.getNext()
+ if (v == None):
+ break
+
+ if (v == 0x72):
+ self.doLoop72('number')
+ elif (v > 0) and (v < self.dict.getSize()) :
+ tag = self.procToken(self.dict.lookup(v))
+ if len(tag) > 0 :
+ self.doc.append(tag)
+ else:
+ if self.debug:
+ print "Main Loop: Unknown value: %x" % v
+ if (v == 0):
+ if (self.peek(1) == 0x5f):
+ skip = self.fo.read(1)
+ first_token = 'info'
+
+ # now do snippet injection
+ if len(self.snippetList) > 0 :
+ if self.debug : print 'Injecting Snippets:'
+ snippet = self.injectSnippets(self.snippetList[0])
+ snipno = snippet[0]
+ tag_add = snippet[1]
+ if self.debug : print self.formatTag(tag_add)
+ if len(tag_add) > 0:
+ self.doc.append(tag_add)
+
+ # handle generation of xml output
+ xmlpage = self.formatDoc(self.flat_xml)
+
+ return xmlpage
+
+
+def fromData(dict, fname):
+ flat_xml = True
+ debug = False
+ pp = PageParser(fname, dict, debug, flat_xml)
+ xmlpage = pp.process()
+ return xmlpage
+
+def getXML(dict, fname):
+ flat_xml = False
+ debug = False
+ pp = PageParser(fname, dict, debug, flat_xml)
+ xmlpage = pp.process()
+ return xmlpage
+
+def usage():
+ print 'Usage: '
+ print ' convert2xml.py dict0000.dat infile.dat '
+ print ' '
+ print ' Options:'
+ print ' -h print this usage help message '
+ print ' -d turn on debug output to check for potential errors '
+ print ' --flat-xml output the flattened xml page description only '
+ print ' '
+ print ' This program will attempt to convert a page*.dat file or '
+ print ' glyphs*.dat file, using the dict0000.dat file, to its xml description. '
+ print ' '
+ print ' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump '
+ print ' the *.dat files from a Topaz format e-book.'
+
+#
+# Main
+#
+
+def main(argv):
+ dictFile = ""
+ pageFile = ""
+ debug = False
+ flat_xml = False
+ printOutput = False
+ if len(argv) == 0:
+ printOutput = True
+ argv = sys.argv
+
+ try:
+ opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"])
+
+ except getopt.GetoptError, err:
+
+ # print help information and exit:
+ print str(err) # will print something like "option -a not recognized"
+ usage()
+ sys.exit(2)
+
+ if len(opts) == 0 and len(args) == 0 :
+ usage()
+ sys.exit(2)
+
+ for o, a in opts:
+ if o =="-d":
+ debug=True
+ if o =="-h":
+ usage()
+ sys.exit(0)
+ if o =="--flat-xml":
+ flat_xml = True
+
+ dictFile, pageFile = args[0], args[1]
+
+ # read in the string table dictionary
+ dict = Dictionary(dictFile)
+ # dict.dumpDict()
+
+ # create a page parser
+ pp = PageParser(pageFile, dict, debug, flat_xml)
+
+ xmlpage = pp.process()
+
+ if printOutput:
+ print xmlpage
+ return 0
+
+ return xmlpage
+
+if __name__ == '__main__':
+ sys.exit(main(''))
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/description.rtfd/TXT.rtf b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/description.rtfd/TXT.rtf
new file mode 100644
index 0000000..4ea1054
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/description.rtfd/TXT.rtf
@@ -0,0 +1,4 @@
+{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
+{\fonttbl}
+{\colortbl;\red255\green255\blue255;}
+}
\ No newline at end of file
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/droplet.rsrc b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/droplet.rsrc
new file mode 100644
index 0000000..6620282
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/droplet.rsrc differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/encodebase64.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/encodebase64.py
new file mode 100755
index 0000000..6bb8c37
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/encodebase64.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# base64.py, version 1.0
+# Copyright © 2010 Apprentice Alf
+
+# Released under the terms of the GNU General Public Licence, version 3 or
+# later.
+
+# Revision history:
+# 1 - Initial release. To allow Applescript to do base64 encoding
+
+"""
+Provide base64 encoding.
+"""
+
+from __future__ import with_statement
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import base64
+
+def usage(progname):
+ print "Applies base64 encoding to the supplied file, sending to standard output"
+ print "Usage:"
+ print " %s " % progname
+
+def cli_main(argv=sys.argv):
+ progname = os.path.basename(argv[0])
+
+ if len(argv)<2:
+ usage(progname)
+ sys.exit(2)
+
+ keypath = argv[1]
+ with open(keypath, 'rb') as f:
+ keyder = f.read()
+ print keyder.encode('base64')
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(cli_main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/erdr2pml.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/erdr2pml.py
new file mode 100755
index 0000000..8f958cd
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/erdr2pml.py
@@ -0,0 +1,526 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+#
+# erdr2pml.py
+#
+# This is a python script. You need a Python interpreter to run it.
+# For example, ActiveState Python, which exists for windows.
+# Changelog
+#
+# Based on ereader2html version 0.08 plus some later small fixes
+#
+# 0.01 - Initial version
+# 0.02 - Support more eReader files. Support bold text and links. Fix PML decoder parsing bug.
+# 0.03 - Fix incorrect variable usage at one place.
+# 0.03b - enhancement by DeBockle (version 259 support)
+# Custom version 0.03 - no change to eReader support, only usability changes
+# - start of pep-8 indentation (spaces not tab), fix trailing blanks
+# - version variable, only one place to change
+# - added main routine, now callable as a library/module,
+# means tools can add optional support for ereader2html
+# - outdir is no longer a mandatory parameter (defaults based on input name if missing)
+# - time taken output to stdout
+# - Psyco support - reduces runtime by a factor of (over) 3!
+# E.g. (~600Kb file) 90 secs down to 24 secs
+# - newstyle classes
+# - changed map call to list comprehension
+# may not work with python 2.3
+# without Psyco this reduces runtime to 90%
+# E.g. 90 secs down to 77 secs
+# Psyco with map calls takes longer, do not run with map in Psyco JIT!
+# - izip calls used instead of zip (if available), further reduction
+# in run time (factor of 4.5).
+# E.g. (~600Kb file) 90 secs down to 20 secs
+# - Python 2.6+ support, avoid DeprecationWarning with sha/sha1
+# 0.04 - Footnote support, PML output, correct charset in html, support more PML tags
+# - Feature change, dump out PML file
+# - Added supprt for footnote tags. NOTE footnote ids appear to be bad (not usable)
+# in some pdb files :-( due to the same id being used multiple times
+# - Added correct charset encoding (pml is based on cp1252)
+# - Added logging support.
+# 0.05 - Improved type 272 support for sidebars, links, chapters, metainfo, etc
+# 0.06 - Merge of 0.04 and 0.05. Improved HTML output
+# Placed images in subfolder, so that it's possible to just
+# drop the book.pml file onto DropBook to make an unencrypted
+# copy of the eReader file.
+# Using that with Calibre works a lot better than the HTML
+# conversion in this code.
+# 0.07 - Further Improved type 272 support for sidebars with all earlier fixes
+# 0.08 - fixed typos, removed extraneous things
+# 0.09 - fixed typos in first_pages to first_page to again support older formats
+# 0.10 - minor cleanups
+# 0.11 - fixups for using correct xml for footnotes and sidebars for use with Dropbook
+# 0.12 - Fix added to prevent lowercasing of image names when the pml code itself uses a different case in the link name.
+# 0.13 - change to unbuffered stdout for use with gui front ends
+# 0.14 - contributed enhancement to support --make-pmlz switch
+# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
+# 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
+# 0.17 - added support for pycrypto's DES as well
+# 0.18 - on Windows try PyCrypto first and OpenSSL next
+# 0.19 - Modify the interface to allow use of import
+# 0.20 - modify to allow use inside new interface for calibre plugins
+# 0.21 - Support eReader (drm) version 11.
+# - Don't reject dictionary format.
+# - Ignore sidebars for dictionaries (different format?)
+
+__version__='0.21'
+
+class Unbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ def write(self, data):
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+import sys
+import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
+
+if 'calibre' in sys.modules:
+ inCalibre = True
+else:
+ inCalibre = False
+
+Des = None
+if sys.platform.startswith('win'):
+ # first try with pycrypto
+ if inCalibre:
+ from calibre_plugins.erdrpdb2pml import pycrypto_des
+ else:
+ import pycrypto_des
+ Des = pycrypto_des.load_pycrypto()
+ if Des == None:
+ # they try with openssl
+ if inCalibre:
+ from calibre_plugins.erdrpdb2pml import openssl_des
+ else:
+ import openssl_des
+ Des = openssl_des.load_libcrypto()
+else:
+ # first try with openssl
+ if inCalibre:
+ from calibre_plugins.erdrpdb2pml import openssl_des
+ else:
+ import openssl_des
+ Des = openssl_des.load_libcrypto()
+ if Des == None:
+ # then try with pycrypto
+ if inCalibre:
+ from calibre_plugins.erdrpdb2pml import pycrypto_des
+ else:
+ import pycrypto_des
+ Des = pycrypto_des.load_pycrypto()
+
+# if that did not work then use pure python implementation
+# of DES and try to speed it up with Psycho
+if Des == None:
+ if inCalibre:
+ from calibre_plugins.erdrpdb2pml import python_des
+ else:
+ import python_des
+ Des = python_des.Des
+ # Import Psyco if available
+ try:
+ # http://psyco.sourceforge.net
+ import psyco
+ psyco.full()
+ except ImportError:
+ pass
+
+try:
+ from hashlib import sha1
+except ImportError:
+ # older Python release
+ import sha
+ sha1 = lambda s: sha.new(s)
+
+import cgi
+import logging
+
+logging.basicConfig()
+#logging.basicConfig(level=logging.DEBUG)
+
+
+class Sectionizer(object):
+ bkType = "Book"
+
+ def __init__(self, filename, ident):
+ self.contents = file(filename, 'rb').read()
+ self.header = self.contents[0:72]
+ self.num_sections, = struct.unpack('>H', self.contents[76:78])
+ # Dictionary or normal content (TODO: Not hard-coded)
+ if self.header[0x3C:0x3C+8] != ident:
+ if self.header[0x3C:0x3C+8] == "PDctPPrs":
+ self.bkType = "Dict"
+ else:
+ raise ValueError('Invalid file format')
+ self.sections = []
+ for i in xrange(self.num_sections):
+ offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8])
+ flags, val = a1, a2<<16|a3<<8|a4
+ self.sections.append( (offset, flags, val) )
+ def loadSection(self, section):
+ if section + 1 == self.num_sections:
+ end_off = len(self.contents)
+ else:
+ end_off = self.sections[section + 1][0]
+ off = self.sections[section][0]
+ return self.contents[off:end_off]
+
+def sanitizeFileName(s):
+ r = ''
+ for c in s:
+ if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-":
+ r += c
+ return r
+
+def fixKey(key):
+ def fixByte(b):
+ return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
+ return "".join([chr(fixByte(ord(a))) for a in key])
+
+def deXOR(text, sp, table):
+ r=''
+ j = sp
+ for i in xrange(len(text)):
+ r += chr(ord(table[j]) ^ ord(text[i]))
+ j = j + 1
+ if j == len(table):
+ j = 0
+ return r
+
+class EreaderProcessor(object):
+ def __init__(self, sect, username, creditcard):
+ self.section_reader = sect.loadSection
+ data = self.section_reader(0)
+ version, = struct.unpack('>H', data[0:2])
+ self.version = version
+ logging.info('eReader file format version %s', version)
+ if version != 272 and version != 260 and version != 259:
+ raise ValueError('incorrect eReader version %d (error 1)' % version)
+ data = self.section_reader(1)
+ self.data = data
+ des = Des(fixKey(data[0:8]))
+ cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:]))
+ if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200:
+ raise ValueError('incorrect eReader version (error 2)')
+ input = des.decrypt(data[-cookie_size:])
+ def unshuff(data, shuf):
+ r = [''] * len(data)
+ j = 0
+ for i in xrange(len(data)):
+ j = (j + shuf) % len(data)
+ r[j] = data[i]
+ assert len("".join(r)) == len(data)
+ return "".join(r)
+ r = unshuff(input[0:-8], cookie_shuf)
+
+ def fixUsername(s):
+ r = ''
+ for c in s.lower():
+ if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'):
+ r += c
+ return r
+
+ user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff)
+ drm_sub_version = struct.unpack('>H', r[0:2])[0]
+ self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1
+ self.num_image_pages = struct.unpack('>H', r[26:26+2])[0]
+ self.first_image_page = struct.unpack('>H', r[24:24+2])[0]
+ # Default values
+ self.num_footnote_pages = 0
+ self.num_sidebar_pages = 0
+ self.first_footnote_page = -1
+ self.first_sidebar_page = -1
+ if self.version == 272:
+ self.num_footnote_pages = struct.unpack('>H', r[46:46+2])[0]
+ self.first_footnote_page = struct.unpack('>H', r[44:44+2])[0]
+ if (sect.bkType == "Book"):
+ self.num_sidebar_pages = struct.unpack('>H', r[38:38+2])[0]
+ self.first_sidebar_page = struct.unpack('>H', r[36:36+2])[0]
+ # self.num_bookinfo_pages = struct.unpack('>H', r[34:34+2])[0]
+ # self.first_bookinfo_page = struct.unpack('>H', r[32:32+2])[0]
+ # self.num_chapter_pages = struct.unpack('>H', r[22:22+2])[0]
+ # self.first_chapter_page = struct.unpack('>H', r[20:20+2])[0]
+ # self.num_link_pages = struct.unpack('>H', r[30:30+2])[0]
+ # self.first_link_page = struct.unpack('>H', r[28:28+2])[0]
+ # self.num_xtextsize_pages = struct.unpack('>H', r[54:54+2])[0]
+ # self.first_xtextsize_page = struct.unpack('>H', r[52:52+2])[0]
+
+ # **before** data record 1 was decrypted and unshuffled, it contained data
+ # to create an XOR table and which is used to fix footnote record 0, link records, chapter records, etc
+ self.xortable_offset = struct.unpack('>H', r[40:40+2])[0]
+ self.xortable_size = struct.unpack('>H', r[42:42+2])[0]
+ self.xortable = self.data[self.xortable_offset:self.xortable_offset + self.xortable_size]
+ else:
+ # Nothing needs to be done
+ pass
+ # self.num_bookinfo_pages = 0
+ # self.num_chapter_pages = 0
+ # self.num_link_pages = 0
+ # self.num_xtextsize_pages = 0
+ # self.first_bookinfo_page = -1
+ # self.first_chapter_page = -1
+ # self.first_link_page = -1
+ # self.first_xtextsize_page = -1
+
+ logging.debug('self.num_text_pages %d', self.num_text_pages)
+ logging.debug('self.num_footnote_pages %d, self.first_footnote_page %d', self.num_footnote_pages , self.first_footnote_page)
+ logging.debug('self.num_sidebar_pages %d, self.first_sidebar_page %d', self.num_sidebar_pages , self.first_sidebar_page)
+ self.flags = struct.unpack('>L', r[4:8])[0]
+ reqd_flags = (1<<9) | (1<<7) | (1<<10)
+ if (self.flags & reqd_flags) != reqd_flags:
+ print "Flags: 0x%X" % self.flags
+ raise ValueError('incompatible eReader file')
+ des = Des(fixKey(user_key))
+ if version == 259:
+ if drm_sub_version != 7:
+ raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
+ encrypted_key_sha = r[44:44+20]
+ encrypted_key = r[64:64+8]
+ elif version == 260:
+ if drm_sub_version != 13 and drm_sub_version != 11:
+ raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
+ if drm_sub_version == 13:
+ encrypted_key = r[44:44+8]
+ encrypted_key_sha = r[52:52+20]
+ else:
+ encrypted_key = r[64:64+8]
+ encrypted_key_sha = r[44:44+20]
+ elif version == 272:
+ encrypted_key = r[172:172+8]
+ encrypted_key_sha = r[56:56+20]
+ self.content_key = des.decrypt(encrypted_key)
+ if sha1(self.content_key).digest() != encrypted_key_sha:
+ raise ValueError('Incorrect Name and/or Credit Card')
+
+ def getNumImages(self):
+ return self.num_image_pages
+
+ def getImage(self, i):
+ sect = self.section_reader(self.first_image_page + i)
+ name = sect[4:4+32].strip('\0')
+ data = sect[62:]
+ return sanitizeFileName(name), data
+
+
+ # def getChapterNamePMLOffsetData(self):
+ # cv = ''
+ # if self.num_chapter_pages > 0:
+ # for i in xrange(self.num_chapter_pages):
+ # chaps = self.section_reader(self.first_chapter_page + i)
+ # j = i % self.xortable_size
+ # offname = deXOR(chaps, j, self.xortable)
+ # offset = struct.unpack('>L', offname[0:4])[0]
+ # name = offname[4:].strip('\0')
+ # cv += '%d|%s\n' % (offset, name)
+ # return cv
+
+ # def getLinkNamePMLOffsetData(self):
+ # lv = ''
+ # if self.num_link_pages > 0:
+ # for i in xrange(self.num_link_pages):
+ # links = self.section_reader(self.first_link_page + i)
+ # j = i % self.xortable_size
+ # offname = deXOR(links, j, self.xortable)
+ # offset = struct.unpack('>L', offname[0:4])[0]
+ # name = offname[4:].strip('\0')
+ # lv += '%d|%s\n' % (offset, name)
+ # return lv
+
+ # def getExpandedTextSizesData(self):
+ # ts = ''
+ # if self.num_xtextsize_pages > 0:
+ # tsize = deXOR(self.section_reader(self.first_xtextsize_page), 0, self.xortable)
+ # for i in xrange(self.num_text_pages):
+ # xsize = struct.unpack('>H', tsize[0:2])[0]
+ # ts += "%d\n" % xsize
+ # tsize = tsize[2:]
+ # return ts
+
+ # def getBookInfo(self):
+ # bkinfo = ''
+ # if self.num_bookinfo_pages > 0:
+ # info = self.section_reader(self.first_bookinfo_page)
+ # bkinfo = deXOR(info, 0, self.xortable)
+ # bkinfo = bkinfo.replace('\0','|')
+ # bkinfo += '\n'
+ # return bkinfo
+
+ def getText(self):
+ des = Des(fixKey(self.content_key))
+ r = ''
+ for i in xrange(self.num_text_pages):
+ logging.debug('get page %d', i)
+ r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
+
+ # now handle footnotes pages
+ if self.num_footnote_pages > 0:
+ r += '\n'
+ # the record 0 of the footnote section must pass through the Xor Table to make it useful
+ sect = self.section_reader(self.first_footnote_page)
+ fnote_ids = deXOR(sect, 0, self.xortable)
+ # the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
+ des = Des(fixKey(self.content_key))
+ for i in xrange(1,self.num_footnote_pages):
+ logging.debug('get footnotepage %d', i)
+ id_len = ord(fnote_ids[2])
+ id = fnote_ids[3:3+id_len]
+ fmarker = '\n' % id
+ fmarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i)))
+ fmarker += '\n \n'
+ r += fmarker
+ fnote_ids = fnote_ids[id_len+4:]
+
+ # TODO: Handle dictionary index (?) pages - which are also marked as
+ # sidebar_pages (?). For now dictionary sidebars are ignored
+ # For dictionaries - record 0 is null terminated strings, followed by
+ # blocks of around 62000 bytes and a final block. Not sure of the
+ # encoding
+
+ # now handle sidebar pages
+ if self.num_sidebar_pages > 0:
+ r += '\n'
+ # the record 0 of the sidebar section must pass through the Xor Table to make it useful
+ sect = self.section_reader(self.first_sidebar_page)
+ sbar_ids = deXOR(sect, 0, self.xortable)
+ # the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
+ des = Des(fixKey(self.content_key))
+ for i in xrange(1,self.num_sidebar_pages):
+ id_len = ord(sbar_ids[2])
+ id = sbar_ids[3:3+id_len]
+ smarker = '\n' % id
+ smarker += zlib.decompress(des.decrypt(self.section_reader(self.first_sidebar_page + i)))
+ smarker += '\n \n'
+ r += smarker
+ sbar_ids = sbar_ids[id_len+4:]
+
+ return r
+
+def cleanPML(pml):
+ # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
+ pml2 = pml
+ for k in xrange(128,256):
+ badChar = chr(k)
+ pml2 = pml2.replace(badChar, '\\a%03d' % k)
+ return pml2
+
+def convertEreaderToPml(infile, name, cc, outdir):
+ if not os.path.exists(outdir):
+ os.makedirs(outdir)
+ bookname = os.path.splitext(os.path.basename(infile))[0]
+ print " Decoding File"
+ sect = Sectionizer(infile, 'PNRdPPrs')
+ er = EreaderProcessor(sect, name, cc)
+
+ if er.getNumImages() > 0:
+ print " Extracting images"
+ imagedir = bookname + '_img/'
+ imagedirpath = os.path.join(outdir,imagedir)
+ if not os.path.exists(imagedirpath):
+ os.makedirs(imagedirpath)
+ for i in xrange(er.getNumImages()):
+ name, contents = er.getImage(i)
+ file(os.path.join(imagedirpath, name), 'wb').write(contents)
+
+ print " Extracting pml"
+ pml_string = er.getText()
+ pmlfilename = bookname + ".pml"
+ file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
+
+ # bkinfo = er.getBookInfo()
+ # if bkinfo != '':
+ # print " Extracting book meta information"
+ # file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
+
+
+
+def decryptBook(infile, outdir, name, cc, make_pmlz):
+ if make_pmlz :
+ # ignore specified outdir, use tempdir instead
+ outdir = tempfile.mkdtemp()
+ try:
+ print "Processing..."
+ convertEreaderToPml(infile, name, cc, outdir)
+ if make_pmlz :
+ import zipfile
+ import shutil
+ print " Creating PMLZ file"
+ zipname = infile[:-4] + '.pmlz'
+ myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False)
+ list = os.listdir(outdir)
+ for file in list:
+ localname = file
+ filePath = os.path.join(outdir,file)
+ if os.path.isfile(filePath):
+ myZipFile.write(filePath, localname)
+ elif os.path.isdir(filePath):
+ imageList = os.listdir(filePath)
+ localimgdir = os.path.basename(filePath)
+ for image in imageList:
+ localname = os.path.join(localimgdir,image)
+ imagePath = os.path.join(filePath,image)
+ if os.path.isfile(imagePath):
+ myZipFile.write(imagePath, localname)
+ myZipFile.close()
+ # remove temporary directory
+ shutil.rmtree(outdir, True)
+ print 'output is %s' % zipname
+ else :
+ print 'output in %s' % outdir
+ print "done"
+ except ValueError, e:
+ print "Error: %s" % e
+ return 1
+ return 0
+
+
+def usage():
+ print "Converts DRMed eReader books to PML Source"
+ print "Usage:"
+ print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
+ print " "
+ print "Options: "
+ print " -h prints this message"
+ print " --make-pmlz create PMLZ instead of using output directory"
+ print " "
+ print "Note:"
+ print " if ommitted, outdir defaults based on 'infile.pdb'"
+ print " It's enough to enter the last 8 digits of the credit card number"
+ return
+
+
+def main(argv=None):
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
+ except getopt.GetoptError, err:
+ print str(err)
+ usage()
+ return 1
+ make_pmlz = False
+ for o, a in opts:
+ if o == "-h":
+ usage()
+ return 0
+ elif o == "--make-pmlz":
+ make_pmlz = True
+
+ print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
+
+ if len(args)!=3 and len(args)!=4:
+ usage()
+ return 1
+
+ if len(args)==3:
+ infile, name, cc = args[0], args[1], args[2]
+ outdir = infile[:-4] + '_Source'
+ elif len(args)==4:
+ infile, outdir, name, cc = args[0], args[1], args[2], args[3]
+
+ return decryptBook(infile, outdir, name, cc, make_pmlz)
+
+
+if __name__ == "__main__":
+ sys.stdout=Unbuffered(sys.stdout)
+ sys.exit(main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2html.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2html.py
new file mode 100755
index 0000000..e5647f4
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2html.py
@@ -0,0 +1,793 @@
+#! /usr/bin/python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+# For use with Topaz Scripts Version 2.6
+
+import sys
+import csv
+import os
+import math
+import getopt
+from struct import pack
+from struct import unpack
+
+
+class DocParser(object):
+ def __init__(self, flatxml, classlst, fileid, bookDir, gdict, fixedimage):
+ self.id = os.path.basename(fileid).replace('.dat','')
+ self.svgcount = 0
+ self.docList = flatxml.split('\n')
+ self.docSize = len(self.docList)
+ self.classList = {}
+ self.bookDir = bookDir
+ self.gdict = gdict
+ tmpList = classlst.split('\n')
+ for pclass in tmpList:
+ if pclass != '':
+ # remove the leading period from the css name
+ cname = pclass[1:]
+ self.classList[cname] = True
+ self.fixedimage = fixedimage
+ self.ocrtext = []
+ self.link_id = []
+ self.link_title = []
+ self.link_page = []
+ self.link_href = []
+ self.link_type = []
+ self.dehyphen_rootid = []
+ self.paracont_stemid = []
+ self.parastems_stemid = []
+
+
+ def getGlyph(self, gid):
+ result = ''
+ id='id="gl%d"' % gid
+ return self.gdict.lookup(id)
+
+ def glyphs_to_image(self, glyphList):
+
+ def extract(path, key):
+ b = path.find(key) + len(key)
+ e = path.find(' ',b)
+ return int(path[b:e])
+
+ svgDir = os.path.join(self.bookDir,'svg')
+
+ imgDir = os.path.join(self.bookDir,'img')
+ imgname = self.id + '_%04d.svg' % self.svgcount
+ imgfile = os.path.join(imgDir,imgname)
+
+ # get glyph information
+ gxList = self.getData('info.glyph.x',0,-1)
+ gyList = self.getData('info.glyph.y',0,-1)
+ gidList = self.getData('info.glyph.glyphID',0,-1)
+
+ gids = []
+ maxws = []
+ maxhs = []
+ xs = []
+ ys = []
+ gdefs = []
+
+ # get path defintions, positions, dimensions for each glyph
+ # that makes up the image, and find min x and min y to reposition origin
+ minx = -1
+ miny = -1
+ for j in glyphList:
+ gid = gidList[j]
+ gids.append(gid)
+
+ xs.append(gxList[j])
+ if minx == -1: minx = gxList[j]
+ else : minx = min(minx, gxList[j])
+
+ ys.append(gyList[j])
+ if miny == -1: miny = gyList[j]
+ else : miny = min(miny, gyList[j])
+
+ path = self.getGlyph(gid)
+ gdefs.append(path)
+
+ maxws.append(extract(path,'width='))
+ maxhs.append(extract(path,'height='))
+
+
+ # change the origin to minx, miny and calc max height and width
+ maxw = maxws[0] + xs[0] - minx
+ maxh = maxhs[0] + ys[0] - miny
+ for j in xrange(0, len(xs)):
+ xs[j] = xs[j] - minx
+ ys[j] = ys[j] - miny
+ maxw = max( maxw, (maxws[j] + xs[j]) )
+ maxh = max( maxh, (maxhs[j] + ys[j]) )
+
+ # open the image file for output
+ ifile = open(imgfile,'w')
+ ifile.write('\n')
+ ifile.write('\n')
+ ifile.write('\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh))
+ ifile.write('\n')
+ for j in xrange(0,len(gdefs)):
+ ifile.write(gdefs[j])
+ ifile.write(' \n')
+ for j in xrange(0,len(gids)):
+ ifile.write(' \n' % (gids[j], xs[j], ys[j]))
+ ifile.write(' ')
+ ifile.close()
+
+ return 0
+
+
+
+ # return tag at line pos in document
+ def lineinDoc(self, pos) :
+ if (pos >= 0) and (pos < self.docSize) :
+ item = self.docList[pos]
+ if item.find('=') >= 0:
+ (name, argres) = item.split('=',1)
+ else :
+ name = item
+ argres = ''
+ return name, argres
+
+
+ # find tag in doc if within pos to end inclusive
+ def findinDoc(self, tagpath, pos, end) :
+ result = None
+ if end == -1 :
+ end = self.docSize
+ else:
+ end = min(self.docSize, end)
+ foundat = -1
+ for j in xrange(pos, end):
+ item = self.docList[j]
+ if item.find('=') >= 0:
+ (name, argres) = item.split('=',1)
+ else :
+ name = item
+ argres = ''
+ if name.endswith(tagpath) :
+ result = argres
+ foundat = j
+ break
+ return foundat, result
+
+
+ # return list of start positions for the tagpath
+ def posinDoc(self, tagpath):
+ startpos = []
+ pos = 0
+ res = ""
+ while res != None :
+ (foundpos, res) = self.findinDoc(tagpath, pos, -1)
+ if res != None :
+ startpos.append(foundpos)
+ pos = foundpos + 1
+ return startpos
+
+
+ # returns a vector of integers for the tagpath
+ def getData(self, tagpath, pos, end):
+ argres=[]
+ (foundat, argt) = self.findinDoc(tagpath, pos, end)
+ if (argt != None) and (len(argt) > 0) :
+ argList = argt.split('|')
+ argres = [ int(strval) for strval in argList]
+ return argres
+
+
+ # get the class
+ def getClass(self, pclass):
+ nclass = pclass
+
+ # class names are an issue given topaz may start them with numerals (not allowed),
+ # use a mix of cases (which cause some browsers problems), and actually
+ # attach numbers after "_reclustered*" to the end to deal classeses that inherit
+ # from a base class (but then not actually provide all of these _reclustereed
+ # classes in the stylesheet!
+
+ # so we clean this up by lowercasing, prepend 'cl-', and getting any baseclass
+ # that exists in the stylesheet first, and then adding this specific class
+ # after
+
+ # also some class names have spaces in them so need to convert to dashes
+ if nclass != None :
+ nclass = nclass.replace(' ','-')
+ classres = ''
+ nclass = nclass.lower()
+ nclass = 'cl-' + nclass
+ baseclass = ''
+ # graphic is the base class for captions
+ if nclass.find('cl-cap-') >=0 :
+ classres = 'graphic' + ' '
+ else :
+ # strip to find baseclass
+ p = nclass.find('_')
+ if p > 0 :
+ baseclass = nclass[0:p]
+ if baseclass in self.classList:
+ classres += baseclass + ' '
+ classres += nclass
+ nclass = classres
+ return nclass
+
+
+ # develop a sorted description of the starting positions of
+ # groups and regions on the page, as well as the page type
+ def PageDescription(self):
+
+ def compare(x, y):
+ (xtype, xval) = x
+ (ytype, yval) = y
+ if xval > yval:
+ return 1
+ if xval == yval:
+ return 0
+ return -1
+
+ result = []
+ (pos, pagetype) = self.findinDoc('page.type',0,-1)
+
+ groupList = self.posinDoc('page.group')
+ groupregionList = self.posinDoc('page.group.region')
+ pageregionList = self.posinDoc('page.region')
+ # integrate into one list
+ for j in groupList:
+ result.append(('grpbeg',j))
+ for j in groupregionList:
+ result.append(('gregion',j))
+ for j in pageregionList:
+ result.append(('pregion',j))
+ result.sort(compare)
+
+ # insert group end and page end indicators
+ inGroup = False
+ j = 0
+ while True:
+ if j == len(result): break
+ rtype = result[j][0]
+ rval = result[j][1]
+ if not inGroup and (rtype == 'grpbeg') :
+ inGroup = True
+ j = j + 1
+ elif inGroup and (rtype in ('grpbeg', 'pregion')):
+ result.insert(j,('grpend',rval))
+ inGroup = False
+ else:
+ j = j + 1
+ if inGroup:
+ result.append(('grpend',-1))
+ result.append(('pageend', -1))
+ return pagetype, result
+
+
+
+ # build a description of the paragraph
+ def getParaDescription(self, start, end, regtype):
+
+ result = []
+
+ # paragraph
+ (pos, pclass) = self.findinDoc('paragraph.class',start,end)
+
+ pclass = self.getClass(pclass)
+
+ # if paragraph uses extratokens (extra glyphs) then make it fixed
+ (pos, extraglyphs) = self.findinDoc('paragraph.extratokens',start,end)
+
+ # build up a description of the paragraph in result and return it
+ # first check for the basic - all words paragraph
+ (pos, sfirst) = self.findinDoc('paragraph.firstWord',start,end)
+ (pos, slast) = self.findinDoc('paragraph.lastWord',start,end)
+ if (sfirst != None) and (slast != None) :
+ first = int(sfirst)
+ last = int(slast)
+
+ makeImage = (regtype == 'vertical') or (regtype == 'table')
+ makeImage = makeImage or (extraglyphs != None)
+ if self.fixedimage:
+ makeImage = makeImage or (regtype == 'fixed')
+
+ if (pclass != None):
+ makeImage = makeImage or (pclass.find('.inverted') >= 0)
+ if self.fixedimage :
+ makeImage = makeImage or (pclass.find('cl-f-') >= 0)
+
+ # before creating an image make sure glyph info exists
+ gidList = self.getData('info.glyph.glyphID',0,-1)
+
+ makeImage = makeImage & (len(gidList) > 0)
+
+ if not makeImage :
+ # standard all word paragraph
+ for wordnum in xrange(first, last):
+ result.append(('ocr', wordnum))
+ return pclass, result
+
+ # convert paragraph to svg image
+ # translate first and last word into first and last glyphs
+ # and generate inline image and include it
+ glyphList = []
+ firstglyphList = self.getData('word.firstGlyph',0,-1)
+ gidList = self.getData('info.glyph.glyphID',0,-1)
+ firstGlyph = firstglyphList[first]
+ if last < len(firstglyphList):
+ lastGlyph = firstglyphList[last]
+ else :
+ lastGlyph = len(gidList)
+
+ # handle case of white sapce paragraphs with no actual glyphs in them
+ # by reverting to text based paragraph
+ if firstGlyph >= lastGlyph:
+ # revert to standard text based paragraph
+ for wordnum in xrange(first, last):
+ result.append(('ocr', wordnum))
+ return pclass, result
+
+ for glyphnum in xrange(firstGlyph, lastGlyph):
+ glyphList.append(glyphnum)
+ # include any extratokens if they exist
+ (pos, sfg) = self.findinDoc('extratokens.firstGlyph',start,end)
+ (pos, slg) = self.findinDoc('extratokens.lastGlyph',start,end)
+ if (sfg != None) and (slg != None):
+ for glyphnum in xrange(int(sfg), int(slg)):
+ glyphList.append(glyphnum)
+ num = self.svgcount
+ self.glyphs_to_image(glyphList)
+ self.svgcount += 1
+ result.append(('svg', num))
+ return pclass, result
+
+ # this type of paragraph may be made up of multiple spans, inline
+ # word monograms (images), and words with semantic meaning,
+ # plus glyphs used to form starting letter of first word
+
+ # need to parse this type line by line
+ line = start + 1
+ word_class = ''
+
+ # if end is -1 then we must search to end of document
+ if end == -1 :
+ end = self.docSize
+
+ # seems some xml has last* coming before first* so we have to
+ # handle any order
+ sp_first = -1
+ sp_last = -1
+
+ gl_first = -1
+ gl_last = -1
+
+ ws_first = -1
+ ws_last = -1
+
+ word_class = ''
+
+ word_semantic_type = ''
+
+ while (line < end) :
+
+ (name, argres) = self.lineinDoc(line)
+
+ if name.endswith('span.firstWord') :
+ sp_first = int(argres)
+
+ elif name.endswith('span.lastWord') :
+ sp_last = int(argres)
+
+ elif name.endswith('word.firstGlyph') :
+ gl_first = int(argres)
+
+ elif name.endswith('word.lastGlyph') :
+ gl_last = int(argres)
+
+ elif name.endswith('word_semantic.firstWord'):
+ ws_first = int(argres)
+
+ elif name.endswith('word_semantic.lastWord'):
+ ws_last = int(argres)
+
+ elif name.endswith('word.class'):
+ (cname, space) = argres.split('-',1)
+ if space == '' : space = '0'
+ if (cname == 'spaceafter') and (int(space) > 0) :
+ word_class = 'sa'
+
+ elif name.endswith('word.img.src'):
+ result.append(('img' + word_class, int(argres)))
+ word_class = ''
+
+ elif name.endswith('region.img.src'):
+ result.append(('img' + word_class, int(argres)))
+
+ if (sp_first != -1) and (sp_last != -1):
+ for wordnum in xrange(sp_first, sp_last):
+ result.append(('ocr', wordnum))
+ sp_first = -1
+ sp_last = -1
+
+ if (gl_first != -1) and (gl_last != -1):
+ glyphList = []
+ for glyphnum in xrange(gl_first, gl_last):
+ glyphList.append(glyphnum)
+ num = self.svgcount
+ self.glyphs_to_image(glyphList)
+ self.svgcount += 1
+ result.append(('svg', num))
+ gl_first = -1
+ gl_last = -1
+
+ if (ws_first != -1) and (ws_last != -1):
+ for wordnum in xrange(ws_first, ws_last):
+ result.append(('ocr', wordnum))
+ ws_first = -1
+ ws_last = -1
+
+ line += 1
+
+ return pclass, result
+
+
+ def buildParagraph(self, pclass, pdesc, type, regtype) :
+ parares = ''
+ sep =''
+
+ classres = ''
+ if pclass :
+ classres = ' class="' + pclass + '"'
+
+ br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical')
+
+ handle_links = len(self.link_id) > 0
+
+ if (type == 'full') or (type == 'begin') :
+ parares += ''
+
+ if (type == 'end'):
+ parares += ' '
+
+ lstart = len(parares)
+
+ cnt = len(pdesc)
+
+ for j in xrange( 0, cnt) :
+
+ (wtype, num) = pdesc[j]
+
+ if wtype == 'ocr' :
+ word = self.ocrtext[num]
+ sep = ' '
+
+ if handle_links:
+ link = self.link_id[num]
+ if (link > 0):
+ linktype = self.link_type[link-1]
+ title = self.link_title[link-1]
+ if (title == "") or (parares.rfind(title) < 0):
+ title=parares[lstart:]
+ if linktype == 'external' :
+ linkhref = self.link_href[link-1]
+ linkhtml = '' % linkhref
+ else :
+ if len(self.link_page) >= link :
+ ptarget = self.link_page[link-1] - 1
+ linkhtml = ' ' % ptarget
+ else :
+ # just link to the current page
+ linkhtml = ' '
+ linkhtml += title + ' '
+ pos = parares.rfind(title)
+ if pos >= 0:
+ parares = parares[0:pos] + linkhtml + parares[pos+len(title):]
+ else :
+ parares += linkhtml
+ lstart = len(parares)
+ if word == '_link_' : word = ''
+ elif (link < 0) :
+ if word == '_link_' : word = ''
+
+ if word == '_lb_':
+ if ((num-1) in self.dehyphen_rootid ) or handle_links:
+ word = ''
+ sep = ''
+ elif br_lb :
+ word = ' \n'
+ sep = ''
+ else :
+ word = '\n'
+ sep = ''
+
+ if num in self.dehyphen_rootid :
+ word = word[0:-1]
+ sep = ''
+
+ parares += word + sep
+
+ elif wtype == 'img' :
+ sep = ''
+ parares += ' ' % num
+ parares += sep
+
+ elif wtype == 'imgsa' :
+ sep = ' '
+ parares += ' ' % num
+ parares += sep
+
+ elif wtype == 'svg' :
+ sep = ''
+ parares += ' ' % num
+ parares += sep
+
+ if len(sep) > 0 : parares = parares[0:-1]
+ if (type == 'full') or (type == 'end') :
+ parares += '
'
+ return parares
+
+
+ def buildTOCEntry(self, pdesc) :
+ parares = ''
+ sep =''
+ tocentry = ''
+ handle_links = len(self.link_id) > 0
+
+ lstart = 0
+
+ cnt = len(pdesc)
+ for j in xrange( 0, cnt) :
+
+ (wtype, num) = pdesc[j]
+
+ if wtype == 'ocr' :
+ word = self.ocrtext[num]
+ sep = ' '
+
+ if handle_links:
+ link = self.link_id[num]
+ if (link > 0):
+ linktype = self.link_type[link-1]
+ title = self.link_title[link-1]
+ title = title.rstrip('. ')
+ alt_title = parares[lstart:]
+ alt_title = alt_title.strip()
+ # now strip off the actual printed page number
+ alt_title = alt_title.rstrip('01234567890ivxldIVXLD-.')
+ alt_title = alt_title.rstrip('. ')
+ # skip over any external links - can't have them in a books toc
+ if linktype == 'external' :
+ title = ''
+ alt_title = ''
+ linkpage = ''
+ else :
+ if len(self.link_page) >= link :
+ ptarget = self.link_page[link-1] - 1
+ linkpage = '%04d' % ptarget
+ else :
+ # just link to the current page
+ linkpage = self.id[4:]
+ if len(alt_title) >= len(title):
+ title = alt_title
+ if title != '' and linkpage != '':
+ tocentry += title + '|' + linkpage + '\n'
+ lstart = len(parares)
+ if word == '_link_' : word = ''
+ elif (link < 0) :
+ if word == '_link_' : word = ''
+
+ if word == '_lb_':
+ word = ''
+ sep = ''
+
+ if num in self.dehyphen_rootid :
+ word = word[0:-1]
+ sep = ''
+
+ parares += word + sep
+
+ else :
+ continue
+
+ return tocentry
+
+
+
+
+ # walk the document tree collecting the information needed
+ # to build an html page using the ocrText
+
+ def process(self):
+
+ tocinfo = ''
+ hlst = []
+
+ # get the ocr text
+ (pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
+ if argres : self.ocrtext = argres.split('|')
+
+ # get information to dehyphenate the text
+ self.dehyphen_rootid = self.getData('info.dehyphen.rootID',0,-1)
+
+ # determine if first paragraph is continued from previous page
+ (pos, self.parastems_stemid) = self.findinDoc('info.paraStems.stemID',0,-1)
+ first_para_continued = (self.parastems_stemid != None)
+
+ # determine if last paragraph is continued onto the next page
+ (pos, self.paracont_stemid) = self.findinDoc('info.paraCont.stemID',0,-1)
+ last_para_continued = (self.paracont_stemid != None)
+
+ # collect link ids
+ self.link_id = self.getData('info.word.link_id',0,-1)
+
+ # collect link destination page numbers
+ self.link_page = self.getData('info.links.page',0,-1)
+
+ # collect link types (container versus external)
+ (pos, argres) = self.findinDoc('info.links.type',0,-1)
+ if argres : self.link_type = argres.split('|')
+
+ # collect link destinations
+ (pos, argres) = self.findinDoc('info.links.href',0,-1)
+ if argres : self.link_href = argres.split('|')
+
+ # collect link titles
+ (pos, argres) = self.findinDoc('info.links.title',0,-1)
+ if argres :
+ self.link_title = argres.split('|')
+ else:
+ self.link_title.append('')
+
+ # get a descriptions of the starting points of the regions
+ # and groups on the page
+ (pagetype, pageDesc) = self.PageDescription()
+ regcnt = len(pageDesc) - 1
+
+ anchorSet = False
+ breakSet = False
+ inGroup = False
+
+ # process each region on the page and convert what you can to html
+
+ for j in xrange(regcnt):
+
+ (etype, start) = pageDesc[j]
+ (ntype, end) = pageDesc[j+1]
+
+
+ # set anchor for link target on this page
+ if not anchorSet and not first_para_continued:
+ hlst.append('
\n')
+ anchorSet = True
+
+ # handle groups of graphics with text captions
+ if (etype == 'grpbeg'):
+ (pos, grptype) = self.findinDoc('group.type', start, end)
+ if grptype != None:
+ if grptype == 'graphic':
+ gcstr = ' class="' + grptype + '"'
+ hlst.append('')
+ inGroup = True
+
+ elif (etype == 'grpend'):
+ if inGroup:
+ hlst.append('
\n')
+ inGroup = False
+
+ else:
+ (pos, regtype) = self.findinDoc('region.type',start,end)
+
+ if regtype == 'graphic' :
+ (pos, simgsrc) = self.findinDoc('img.src',start,end)
+ if simgsrc:
+ if inGroup:
+ hlst.append(' ' % int(simgsrc))
+ else:
+ hlst.append('' % int(simgsrc))
+
+ elif regtype == 'chapterheading' :
+ (pclass, pdesc) = self.getParaDescription(start,end, regtype)
+ if not breakSet:
+ hlst.append('
\n')
+ breakSet = True
+ tag = 'h1'
+ if pclass and (len(pclass) >= 7):
+ if pclass[3:7] == 'ch1-' : tag = 'h1'
+ if pclass[3:7] == 'ch2-' : tag = 'h2'
+ if pclass[3:7] == 'ch3-' : tag = 'h3'
+ hlst.append('<' + tag + ' class="' + pclass + '">')
+ else:
+ hlst.append('<' + tag + '>')
+ hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
+ hlst.append('' + tag + '>')
+
+ elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'):
+ ptype = 'full'
+ # check to see if this is a continution from the previous page
+ if first_para_continued :
+ ptype = 'end'
+ first_para_continued = False
+ (pclass, pdesc) = self.getParaDescription(start,end, regtype)
+ if pclass and (len(pclass) >= 6) and (ptype == 'full'):
+ tag = 'p'
+ if pclass[3:6] == 'h1-' : tag = 'h4'
+ if pclass[3:6] == 'h2-' : tag = 'h5'
+ if pclass[3:6] == 'h3-' : tag = 'h6'
+ hlst.append('<' + tag + ' class="' + pclass + '">')
+ hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
+ hlst.append('' + tag + '>')
+ else :
+ hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
+
+ elif (regtype == 'tocentry') :
+ ptype = 'full'
+ if first_para_continued :
+ ptype = 'end'
+ first_para_continued = False
+ (pclass, pdesc) = self.getParaDescription(start,end, regtype)
+ tocinfo += self.buildTOCEntry(pdesc)
+ hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
+
+ elif (regtype == 'vertical') or (regtype == 'table') :
+ ptype = 'full'
+ if inGroup:
+ ptype = 'middle'
+ if first_para_continued :
+ ptype = 'end'
+ first_para_continued = False
+ (pclass, pdesc) = self.getParaDescription(start, end, regtype)
+ hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
+
+
+ elif (regtype == 'synth_fcvr.center'):
+ (pos, simgsrc) = self.findinDoc('img.src',start,end)
+ if simgsrc:
+ hlst.append('' % int(simgsrc))
+
+ else :
+ print ' Making region type', regtype,
+ (pos, temp) = self.findinDoc('paragraph',start,end)
+ (pos2, temp) = self.findinDoc('span',start,end)
+ if pos != -1 or pos2 != -1:
+ print ' a "text" region'
+ orig_regtype = regtype
+ regtype = 'fixed'
+ ptype = 'full'
+ # check to see if this is a continution from the previous page
+ if first_para_continued :
+ ptype = 'end'
+ first_para_continued = False
+ (pclass, pdesc) = self.getParaDescription(start,end, regtype)
+ if not pclass:
+ if orig_regtype.endswith('.right') : pclass = 'cl-right'
+ elif orig_regtype.endswith('.center') : pclass = 'cl-center'
+ elif orig_regtype.endswith('.left') : pclass = 'cl-left'
+ elif orig_regtype.endswith('.justify') : pclass = 'cl-justify'
+ if pclass and (ptype == 'full') and (len(pclass) >= 6):
+ tag = 'p'
+ if pclass[3:6] == 'h1-' : tag = 'h4'
+ if pclass[3:6] == 'h2-' : tag = 'h5'
+ if pclass[3:6] == 'h3-' : tag = 'h6'
+ hlst.append('<' + tag + ' class="' + pclass + '">')
+ hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
+ hlst.append('' + tag + '>')
+ else :
+ hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
+ else :
+ print ' a "graphic" region'
+ (pos, simgsrc) = self.findinDoc('img.src',start,end)
+ if simgsrc:
+ hlst.append('' % int(simgsrc))
+
+
+ htmlpage = "".join(hlst)
+ if last_para_continued :
+ if htmlpage[-4:] == '
':
+ htmlpage = htmlpage[0:-4]
+ last_para_continued = False
+
+ return htmlpage, tocinfo
+
+
+def convert2HTML(flatxml, classlst, fileid, bookDir, gdict, fixedimage):
+ # create a document parser
+ dp = DocParser(flatxml, classlst, fileid, bookDir, gdict, fixedimage)
+ htmlpage, tocinfo = dp.process()
+ return htmlpage, tocinfo
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2svg.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2svg.py
new file mode 100755
index 0000000..4dfd6c7
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/flatxml2svg.py
@@ -0,0 +1,249 @@
+#! /usr/bin/python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+import sys
+import csv
+import os
+import getopt
+from struct import pack
+from struct import unpack
+
+
+class PParser(object):
+ def __init__(self, gd, flatxml, meta_array):
+ self.gd = gd
+ self.flatdoc = flatxml.split('\n')
+ self.docSize = len(self.flatdoc)
+ self.temp = []
+
+ self.ph = -1
+ self.pw = -1
+ startpos = self.posinDoc('page.h') or self.posinDoc('book.h')
+ for p in startpos:
+ (name, argres) = self.lineinDoc(p)
+ self.ph = max(self.ph, int(argres))
+ startpos = self.posinDoc('page.w') or self.posinDoc('book.w')
+ for p in startpos:
+ (name, argres) = self.lineinDoc(p)
+ self.pw = max(self.pw, int(argres))
+
+ if self.ph <= 0:
+ self.ph = int(meta_array.get('pageHeight', '11000'))
+ if self.pw <= 0:
+ self.pw = int(meta_array.get('pageWidth', '8500'))
+
+ res = []
+ startpos = self.posinDoc('info.glyph.x')
+ for p in startpos:
+ argres = self.getDataatPos('info.glyph.x', p)
+ res.extend(argres)
+ self.gx = res
+
+ res = []
+ startpos = self.posinDoc('info.glyph.y')
+ for p in startpos:
+ argres = self.getDataatPos('info.glyph.y', p)
+ res.extend(argres)
+ self.gy = res
+
+ res = []
+ startpos = self.posinDoc('info.glyph.glyphID')
+ for p in startpos:
+ argres = self.getDataatPos('info.glyph.glyphID', p)
+ res.extend(argres)
+ self.gid = res
+
+
+ # return tag at line pos in document
+ def lineinDoc(self, pos) :
+ if (pos >= 0) and (pos < self.docSize) :
+ item = self.flatdoc[pos]
+ if item.find('=') >= 0:
+ (name, argres) = item.split('=',1)
+ else :
+ name = item
+ argres = ''
+ return name, argres
+
+ # find tag in doc if within pos to end inclusive
+ def findinDoc(self, tagpath, pos, end) :
+ result = None
+ if end == -1 :
+ end = self.docSize
+ else:
+ end = min(self.docSize, end)
+ foundat = -1
+ for j in xrange(pos, end):
+ item = self.flatdoc[j]
+ if item.find('=') >= 0:
+ (name, argres) = item.split('=',1)
+ else :
+ name = item
+ argres = ''
+ if name.endswith(tagpath) :
+ result = argres
+ foundat = j
+ break
+ return foundat, result
+
+ # return list of start positions for the tagpath
+ def posinDoc(self, tagpath):
+ startpos = []
+ pos = 0
+ res = ""
+ while res != None :
+ (foundpos, res) = self.findinDoc(tagpath, pos, -1)
+ if res != None :
+ startpos.append(foundpos)
+ pos = foundpos + 1
+ return startpos
+
+ def getData(self, path):
+ result = None
+ cnt = len(self.flatdoc)
+ for j in xrange(cnt):
+ item = self.flatdoc[j]
+ if item.find('=') >= 0:
+ (name, argt) = item.split('=')
+ argres = argt.split('|')
+ else:
+ name = item
+ argres = []
+ if (name.endswith(path)):
+ result = argres
+ break
+ if (len(argres) > 0) :
+ for j in xrange(0,len(argres)):
+ argres[j] = int(argres[j])
+ return result
+
+ def getDataatPos(self, path, pos):
+ result = None
+ item = self.flatdoc[pos]
+ if item.find('=') >= 0:
+ (name, argt) = item.split('=')
+ argres = argt.split('|')
+ else:
+ name = item
+ argres = []
+ if (len(argres) > 0) :
+ for j in xrange(0,len(argres)):
+ argres[j] = int(argres[j])
+ if (name.endswith(path)):
+ result = argres
+ return result
+
+ def getDataTemp(self, path):
+ result = None
+ cnt = len(self.temp)
+ for j in xrange(cnt):
+ item = self.temp[j]
+ if item.find('=') >= 0:
+ (name, argt) = item.split('=')
+ argres = argt.split('|')
+ else:
+ name = item
+ argres = []
+ if (name.endswith(path)):
+ result = argres
+ self.temp.pop(j)
+ break
+ if (len(argres) > 0) :
+ for j in xrange(0,len(argres)):
+ argres[j] = int(argres[j])
+ return result
+
+ def getImages(self):
+ result = []
+ self.temp = self.flatdoc
+ while (self.getDataTemp('img') != None):
+ h = self.getDataTemp('img.h')[0]
+ w = self.getDataTemp('img.w')[0]
+ x = self.getDataTemp('img.x')[0]
+ y = self.getDataTemp('img.y')[0]
+ src = self.getDataTemp('img.src')[0]
+ result.append(' \n' % (src, x, y, w, h))
+ return result
+
+ def getGlyphs(self):
+ result = []
+ if (self.gid != None) and (len(self.gid) > 0):
+ glyphs = []
+ for j in set(self.gid):
+ glyphs.append(j)
+ glyphs.sort()
+ for gid in glyphs:
+ id='id="gl%d"' % gid
+ path = self.gd.lookup(id)
+ if path:
+ result.append(id + ' ' + path)
+ return result
+
+
+def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi):
+ mlst = []
+ pp = PParser(gdict, flat_xml, meta_array)
+ mlst.append('\n')
+ if (raw):
+ mlst.append('\n')
+ mlst.append('\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1))
+ mlst.append('Page %d - %s by %s \n' % (pageid, meta_array['Title'],meta_array['Authors']))
+ else:
+ mlst.append('\n')
+ mlst.append('\n')
+ mlst.append('Page %d - %s by %s \n' % (pageid, meta_array['Title'],meta_array['Authors']))
+ mlst.append('\n')
+ mlst.append('\n')
+ mlst.append('\n')
+ mlst.append('\n')
+ mlst.append('\n')
+ mlst.append('\n')
+ mlst.append('\n')
+ return "".join(mlst)
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/genbook.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/genbook.py
new file mode 100755
index 0000000..9733887
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/genbook.py
@@ -0,0 +1,721 @@
+#! /usr/bin/python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+class Unbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ def write(self, data):
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+import sys
+sys.stdout=Unbuffered(sys.stdout)
+
+import csv
+import os
+import getopt
+from struct import pack
+from struct import unpack
+
+class TpzDRMError(Exception):
+ pass
+
+# local support routines
+if 'calibre' in sys.modules:
+ inCalibre = True
+else:
+ inCalibre = False
+
+if inCalibre :
+ from calibre_plugins.k4mobidedrm import convert2xml
+ from calibre_plugins.k4mobidedrm import flatxml2html
+ from calibre_plugins.k4mobidedrm import flatxml2svg
+ from calibre_plugins.k4mobidedrm import stylexml2css
+else :
+ import convert2xml
+ import flatxml2html
+ import flatxml2svg
+ import stylexml2css
+
+# global switch
+buildXML = False
+
+# Get a 7 bit encoded number from a file
+def readEncodedNumber(file):
+ flag = False
+ c = file.read(1)
+ if (len(c) == 0):
+ return None
+ data = ord(c)
+ if data == 0xFF:
+ flag = True
+ c = file.read(1)
+ if (len(c) == 0):
+ return None
+ data = ord(c)
+ if data >= 0x80:
+ datax = (data & 0x7F)
+ while data >= 0x80 :
+ c = file.read(1)
+ if (len(c) == 0):
+ return None
+ data = ord(c)
+ datax = (datax <<7) + (data & 0x7F)
+ data = datax
+ if flag:
+ data = -data
+ return data
+
+# Get a length prefixed string from the file
+def lengthPrefixString(data):
+ return encodeNumber(len(data))+data
+
+def readString(file):
+ stringLength = readEncodedNumber(file)
+ if (stringLength == None):
+ return None
+ sv = file.read(stringLength)
+ if (len(sv) != stringLength):
+ return ""
+ return unpack(str(stringLength)+"s",sv)[0]
+
+def getMetaArray(metaFile):
+ # parse the meta file
+ result = {}
+ fo = file(metaFile,'rb')
+ size = readEncodedNumber(fo)
+ for i in xrange(size):
+ tag = readString(fo)
+ value = readString(fo)
+ result[tag] = value
+ # print tag, value
+ fo.close()
+ return result
+
+
+# dictionary of all text strings by index value
+class Dictionary(object):
+ def __init__(self, dictFile):
+ self.filename = dictFile
+ self.size = 0
+ self.fo = file(dictFile,'rb')
+ self.stable = []
+ self.size = readEncodedNumber(self.fo)
+ for i in xrange(self.size):
+ self.stable.append(self.escapestr(readString(self.fo)))
+ self.pos = 0
+ def escapestr(self, str):
+ str = str.replace('&','&')
+ str = str.replace('<','<')
+ str = str.replace('>','>')
+ str = str.replace('=','=')
+ return str
+ def lookup(self,val):
+ if ((val >= 0) and (val < self.size)) :
+ self.pos = val
+ return self.stable[self.pos]
+ else:
+ print "Error - %d outside of string table limits" % val
+ raise TpzDRMError('outside or string table limits')
+ # sys.exit(-1)
+ def getSize(self):
+ return self.size
+ def getPos(self):
+ return self.pos
+
+
+class PageDimParser(object):
+ def __init__(self, flatxml):
+ self.flatdoc = flatxml.split('\n')
+ # find tag if within pos to end inclusive
+ def findinDoc(self, tagpath, pos, end) :
+ result = None
+ docList = self.flatdoc
+ cnt = len(docList)
+ if end == -1 :
+ end = cnt
+ else:
+ end = min(cnt,end)
+ foundat = -1
+ for j in xrange(pos, end):
+ item = docList[j]
+ if item.find('=') >= 0:
+ (name, argres) = item.split('=')
+ else :
+ name = item
+ argres = ''
+ if name.endswith(tagpath) :
+ result = argres
+ foundat = j
+ break
+ return foundat, result
+ def process(self):
+ (pos, sph) = self.findinDoc('page.h',0,-1)
+ (pos, spw) = self.findinDoc('page.w',0,-1)
+ if (sph == None): sph = '-1'
+ if (spw == None): spw = '-1'
+ return sph, spw
+
+def getPageDim(flatxml):
+ # create a document parser
+ dp = PageDimParser(flatxml)
+ (ph, pw) = dp.process()
+ return ph, pw
+
+class GParser(object):
+ def __init__(self, flatxml):
+ self.flatdoc = flatxml.split('\n')
+ self.dpi = 1440
+ self.gh = self.getData('info.glyph.h')
+ self.gw = self.getData('info.glyph.w')
+ self.guse = self.getData('info.glyph.use')
+ if self.guse :
+ self.count = len(self.guse)
+ else :
+ self.count = 0
+ self.gvtx = self.getData('info.glyph.vtx')
+ self.glen = self.getData('info.glyph.len')
+ self.gdpi = self.getData('info.glyph.dpi')
+ self.vx = self.getData('info.vtx.x')
+ self.vy = self.getData('info.vtx.y')
+ self.vlen = self.getData('info.len.n')
+ if self.vlen :
+ self.glen.append(len(self.vlen))
+ elif self.glen:
+ self.glen.append(0)
+ if self.vx :
+ self.gvtx.append(len(self.vx))
+ elif self.gvtx :
+ self.gvtx.append(0)
+ def getData(self, path):
+ result = None
+ cnt = len(self.flatdoc)
+ for j in xrange(cnt):
+ item = self.flatdoc[j]
+ if item.find('=') >= 0:
+ (name, argt) = item.split('=')
+ argres = argt.split('|')
+ else:
+ name = item
+ argres = []
+ if (name == path):
+ result = argres
+ break
+ if (len(argres) > 0) :
+ for j in xrange(0,len(argres)):
+ argres[j] = int(argres[j])
+ return result
+ def getGlyphDim(self, gly):
+ if self.gdpi[gly] == 0:
+ return 0, 0
+ maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly]
+ maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly]
+ return maxh, maxw
+ def getPath(self, gly):
+ path = ''
+ if (gly < 0) or (gly >= self.count):
+ return path
+ tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]]
+ ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]]
+ p = 0
+ for k in xrange(self.glen[gly], self.glen[gly+1]):
+ if (p == 0):
+ zx = tx[0:self.vlen[k]+1]
+ zy = ty[0:self.vlen[k]+1]
+ else:
+ zx = tx[self.vlen[k-1]+1:self.vlen[k]+1]
+ zy = ty[self.vlen[k-1]+1:self.vlen[k]+1]
+ p += 1
+ j = 0
+ while ( j < len(zx) ):
+ if (j == 0):
+ # Start Position.
+ path += 'M %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly])
+ elif (j <= len(zx)-3):
+ # Cubic Bezier Curve
+ path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[j+2] * self.dpi / self.gdpi[gly], zy[j+2] * self.dpi / self.gdpi[gly])
+ j += 2
+ elif (j == len(zx)-2):
+ # Cubic Bezier Curve to Start Position
+ path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly])
+ j += 1
+ elif (j == len(zx)-1):
+ # Quadratic Bezier Curve to Start Position
+ path += 'Q %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly])
+
+ j += 1
+ path += 'z'
+ return path
+
+
+
+# dictionary of all text strings by index value
+class GlyphDict(object):
+ def __init__(self):
+ self.gdict = {}
+ def lookup(self, id):
+ # id='id="gl%d"' % val
+ if id in self.gdict:
+ return self.gdict[id]
+ return None
+ def addGlyph(self, val, path):
+ id='id="gl%d"' % val
+ self.gdict[id] = path
+
+
+def generateBook(bookDir, raw, fixedimage):
+ # sanity check Topaz file extraction
+ if not os.path.exists(bookDir) :
+ print "Can not find directory with unencrypted book"
+ return 1
+
+ dictFile = os.path.join(bookDir,'dict0000.dat')
+ if not os.path.exists(dictFile) :
+ print "Can not find dict0000.dat file"
+ return 1
+
+ pageDir = os.path.join(bookDir,'page')
+ if not os.path.exists(pageDir) :
+ print "Can not find page directory in unencrypted book"
+ return 1
+
+ imgDir = os.path.join(bookDir,'img')
+ if not os.path.exists(imgDir) :
+ print "Can not find image directory in unencrypted book"
+ return 1
+
+ glyphsDir = os.path.join(bookDir,'glyphs')
+ if not os.path.exists(glyphsDir) :
+ print "Can not find glyphs directory in unencrypted book"
+ return 1
+
+ metaFile = os.path.join(bookDir,'metadata0000.dat')
+ if not os.path.exists(metaFile) :
+ print "Can not find metadata0000.dat in unencrypted book"
+ return 1
+
+ svgDir = os.path.join(bookDir,'svg')
+ if not os.path.exists(svgDir) :
+ os.makedirs(svgDir)
+
+ if buildXML:
+ xmlDir = os.path.join(bookDir,'xml')
+ if not os.path.exists(xmlDir) :
+ os.makedirs(xmlDir)
+
+ otherFile = os.path.join(bookDir,'other0000.dat')
+ if not os.path.exists(otherFile) :
+ print "Can not find other0000.dat in unencrypted book"
+ return 1
+
+ print "Updating to color images if available"
+ spath = os.path.join(bookDir,'color_img')
+ dpath = os.path.join(bookDir,'img')
+ filenames = os.listdir(spath)
+ filenames = sorted(filenames)
+ for filename in filenames:
+ imgname = filename.replace('color','img')
+ sfile = os.path.join(spath,filename)
+ dfile = os.path.join(dpath,imgname)
+ imgdata = file(sfile,'rb').read()
+ file(dfile,'wb').write(imgdata)
+
+ print "Creating cover.jpg"
+ isCover = False
+ cpath = os.path.join(bookDir,'img')
+ cpath = os.path.join(cpath,'img0000.jpg')
+ if os.path.isfile(cpath):
+ cover = file(cpath, 'rb').read()
+ cpath = os.path.join(bookDir,'cover.jpg')
+ file(cpath, 'wb').write(cover)
+ isCover = True
+
+
+ print 'Processing Dictionary'
+ dict = Dictionary(dictFile)
+
+ print 'Processing Meta Data and creating OPF'
+ meta_array = getMetaArray(metaFile)
+
+ # replace special chars in title and authors like & < >
+ title = meta_array.get('Title','No Title Provided')
+ title = title.replace('&','&')
+ title = title.replace('<','<')
+ title = title.replace('>','>')
+ meta_array['Title'] = title
+ authors = meta_array.get('Authors','No Authors Provided')
+ authors = authors.replace('&','&')
+ authors = authors.replace('<','<')
+ authors = authors.replace('>','>')
+ meta_array['Authors'] = authors
+
+ if buildXML:
+ xname = os.path.join(xmlDir, 'metadata.xml')
+ mlst = []
+ for key in meta_array:
+ mlst.append(' \n')
+ metastr = "".join(mlst)
+ mlst = None
+ file(xname, 'wb').write(metastr)
+
+ print 'Processing StyleSheet'
+
+ # get some scaling info from metadata to use while processing styles
+ # and first page info
+
+ fontsize = '135'
+ if 'fontSize' in meta_array:
+ fontsize = meta_array['fontSize']
+
+ # also get the size of a normal text page
+ # get the total number of pages unpacked as a safety check
+ filenames = os.listdir(pageDir)
+ numfiles = len(filenames)
+
+ spage = '1'
+ if 'firstTextPage' in meta_array:
+ spage = meta_array['firstTextPage']
+ pnum = int(spage)
+ if pnum >= numfiles or pnum < 0:
+ # metadata is wrong so just select a page near the front
+ # 10% of the book to get a normal text page
+ pnum = int(0.10 * numfiles)
+ # print "first normal text page is", spage
+
+ # get page height and width from first text page for use in stylesheet scaling
+ pname = 'page%04d.dat' % (pnum + 1)
+ fname = os.path.join(pageDir,pname)
+ flat_xml = convert2xml.fromData(dict, fname)
+
+ (ph, pw) = getPageDim(flat_xml)
+ if (ph == '-1') or (ph == '0') : ph = '11000'
+ if (pw == '-1') or (pw == '0') : pw = '8500'
+ meta_array['pageHeight'] = ph
+ meta_array['pageWidth'] = pw
+ if 'fontSize' not in meta_array.keys():
+ meta_array['fontSize'] = fontsize
+
+ # process other.dat for css info and for map of page files to svg images
+ # this map is needed because some pages actually are made up of multiple
+ # pageXXXX.xml files
+ xname = os.path.join(bookDir, 'style.css')
+ flat_xml = convert2xml.fromData(dict, otherFile)
+
+ # extract info.original.pid to get original page information
+ pageIDMap = {}
+ pageidnums = stylexml2css.getpageIDMap(flat_xml)
+ if len(pageidnums) == 0:
+ filenames = os.listdir(pageDir)
+ numfiles = len(filenames)
+ for k in range(numfiles):
+ pageidnums.append(k)
+ # create a map from page ids to list of page file nums to process for that page
+ for i in range(len(pageidnums)):
+ id = pageidnums[i]
+ if id in pageIDMap.keys():
+ pageIDMap[id].append(i)
+ else:
+ pageIDMap[id] = [i]
+
+ # now get the css info
+ cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
+ file(xname, 'wb').write(cssstr)
+ if buildXML:
+ xname = os.path.join(xmlDir, 'other0000.xml')
+ file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
+
+ print 'Processing Glyphs'
+ gd = GlyphDict()
+ filenames = os.listdir(glyphsDir)
+ filenames = sorted(filenames)
+ glyfname = os.path.join(svgDir,'glyphs.svg')
+ glyfile = open(glyfname, 'w')
+ glyfile.write('\n')
+ glyfile.write('\n')
+ glyfile.write('\n')
+ glyfile.write('Glyphs for %s \n' % meta_array['Title'])
+ glyfile.write('\n')
+ counter = 0
+ for filename in filenames:
+ # print ' ', filename
+ print '.',
+ fname = os.path.join(glyphsDir,filename)
+ flat_xml = convert2xml.fromData(dict, fname)
+
+ if buildXML:
+ xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
+ file(xname, 'wb').write(convert2xml.getXML(dict, fname))
+
+ gp = GParser(flat_xml)
+ for i in xrange(0, gp.count):
+ path = gp.getPath(i)
+ maxh, maxw = gp.getGlyphDim(i)
+ fullpath = ' \n' % (counter * 256 + i, path, maxw, maxh)
+ glyfile.write(fullpath)
+ gd.addGlyph(counter * 256 + i, fullpath)
+ counter += 1
+ glyfile.write(' \n')
+ glyfile.write(' \n')
+ glyfile.close()
+ print " "
+
+
+ # start up the html
+ # also build up tocentries while processing html
+ htmlFileName = "book.html"
+ hlst = []
+ hlst.append('\n')
+ hlst.append('\n')
+ hlst.append('\n')
+ hlst.append('\n')
+ hlst.append(' \n')
+ hlst.append('' + meta_array['Title'] + ' by ' + meta_array['Authors'] + ' \n')
+ hlst.append(' \n')
+ hlst.append(' \n')
+ if 'ASIN' in meta_array:
+ hlst.append(' \n')
+ if 'GUID' in meta_array:
+ hlst.append(' \n')
+ hlst.append(' \n')
+ hlst.append('\n\n')
+
+ print 'Processing Pages'
+ # Books are at 1440 DPI. This is rendering at twice that size for
+ # readability when rendering to the screen.
+ scaledpi = 1440.0
+
+ filenames = os.listdir(pageDir)
+ filenames = sorted(filenames)
+ numfiles = len(filenames)
+
+ xmllst = []
+ elst = []
+
+ for filename in filenames:
+ # print ' ', filename
+ print ".",
+ fname = os.path.join(pageDir,filename)
+ flat_xml = convert2xml.fromData(dict, fname)
+
+ # keep flat_xml for later svg processing
+ xmllst.append(flat_xml)
+
+ if buildXML:
+ xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
+ file(xname, 'wb').write(convert2xml.getXML(dict, fname))
+
+ # first get the html
+ pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
+ elst.append(tocinfo)
+ hlst.append(pagehtml)
+
+ # finish up the html string and output it
+ hlst.append('\n\n')
+ htmlstr = "".join(hlst)
+ hlst = None
+ file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
+
+ print " "
+ print 'Extracting Table of Contents from Amazon OCR'
+
+ # first create a table of contents file for the svg images
+ tlst = []
+ tlst.append('\n')
+ tlst.append('\n')
+ tlst.append('')
+ tlst.append('\n')
+ tlst.append('' + meta_array['Title'] + ' \n')
+ tlst.append(' \n')
+ tlst.append(' \n')
+ if 'ASIN' in meta_array:
+ tlst.append(' \n')
+ if 'GUID' in meta_array:
+ tlst.append(' \n')
+ tlst.append('\n')
+ tlst.append('\n')
+
+ tlst.append('Table of Contents \n')
+ start = pageidnums[0]
+ if (raw):
+ startname = 'page%04d.svg' % start
+ else:
+ startname = 'page%04d.xhtml' % start
+
+ tlst.append('\n')
+ # build up a table of contents for the svg xhtml output
+ tocentries = "".join(elst)
+ elst = None
+ toclst = tocentries.split('\n')
+ toclst.pop()
+ for entry in toclst:
+ print entry
+ title, pagenum = entry.split('|')
+ id = pageidnums[int(pagenum)]
+ if (raw):
+ fname = 'page%04d.svg' % id
+ else:
+ fname = 'page%04d.xhtml' % id
+ tlst.append('\n')
+ tlst.append('\n')
+ tlst.append('\n')
+ tochtml = "".join(tlst)
+ file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml)
+
+
+ # now create index_svg.xhtml that points to all required files
+ slst = []
+ slst.append('\n')
+ slst.append('\n')
+ slst.append('')
+ slst.append('\n')
+ slst.append('' + meta_array['Title'] + ' \n')
+ slst.append(' \n')
+ slst.append(' \n')
+ if 'ASIN' in meta_array:
+ slst.append(' \n')
+ if 'GUID' in meta_array:
+ slst.append(' \n')
+ slst.append('\n')
+ slst.append('\n')
+
+ print "Building svg images of each book page"
+ slst.append('List of Pages \n')
+ slst.append('\n')
+ idlst = sorted(pageIDMap.keys())
+ numids = len(idlst)
+ cnt = len(idlst)
+ previd = None
+ for j in range(cnt):
+ pageid = idlst[j]
+ if j < cnt - 1:
+ nextid = idlst[j+1]
+ else:
+ nextid = None
+ print '.',
+ pagelst = pageIDMap[pageid]
+ flst = []
+ for page in pagelst:
+ flst.append(xmllst[page])
+ flat_svg = "".join(flst)
+ flst=None
+ svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi)
+ if (raw) :
+ pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w')
+ slst.append('
Page %d \n' % (pageid, pageid))
+ else :
+ pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w')
+ slst.append('
Page %d \n' % (pageid, pageid))
+ previd = pageid
+ pfile.write(svgxml)
+ pfile.close()
+ counter += 1
+ slst.append('
\n')
+ slst.append('\n')
+ slst.append('\n\n')
+ svgindex = "".join(slst)
+ slst = None
+ file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
+
+ print " "
+
+ # build the opf file
+ opfname = os.path.join(bookDir, 'book.opf')
+ olst = []
+ olst.append('\n')
+ olst.append('\n')
+ # adding metadata
+ olst.append(' \n')
+ if 'GUID' in meta_array:
+ olst.append(' ' + meta_array['GUID'] + ' \n')
+ if 'ASIN' in meta_array:
+ olst.append(' ' + meta_array['ASIN'] + ' \n')
+ if 'oASIN' in meta_array:
+ olst.append(' ' + meta_array['oASIN'] + ' \n')
+ olst.append(' ' + meta_array['Title'] + ' \n')
+ olst.append(' ' + meta_array['Authors'] + ' \n')
+ olst.append(' en \n')
+ olst.append(' ' + meta_array['UpdateTime'] + ' \n')
+ if isCover:
+ olst.append(' \n')
+ olst.append(' \n')
+ olst.append('\n')
+ olst.append(' \n')
+ olst.append(' \n')
+ # adding image files to manifest
+ filenames = os.listdir(imgDir)
+ filenames = sorted(filenames)
+ for filename in filenames:
+ imgname, imgext = os.path.splitext(filename)
+ if imgext == '.jpg':
+ imgext = 'jpeg'
+ if imgext == '.svg':
+ imgext = 'svg+xml'
+ olst.append(' \n')
+ if isCover:
+ olst.append(' \n')
+ olst.append(' \n')
+ # adding spine
+ olst.append('\n \n \n')
+ if isCover:
+ olst.append(' \n')
+ olst.append(' \n')
+ olst.append(' \n')
+ olst.append(' \n')
+ opfstr = "".join(olst)
+ olst = None
+ file(opfname, 'wb').write(opfstr)
+
+ print 'Processing Complete'
+
+ return 0
+
+def usage():
+ print "genbook.py generates a book from the extract Topaz Files"
+ print "Usage:"
+ print " genbook.py [-r] [-h [--fixed-image] "
+ print " "
+ print "Options:"
+ print " -h : help - print this usage message"
+ print " -r : generate raw svg files (not wrapped in xhtml)"
+ print " --fixed-image : genearate any Fixed Area as an svg image in the html"
+ print " "
+
+
+def main(argv):
+ bookDir = ''
+ if len(argv) == 0:
+ argv = sys.argv
+
+ try:
+ opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
+
+ except getopt.GetoptError, err:
+ print str(err)
+ usage()
+ return 1
+
+ if len(opts) == 0 and len(args) == 0 :
+ usage()
+ return 1
+
+ raw = 0
+ fixedimage = True
+ for o, a in opts:
+ if o =="-h":
+ usage()
+ return 0
+ if o =="-r":
+ raw = 1
+ if o =="--fixed-image":
+ fixedimage = True
+
+ bookDir = args[0]
+
+ rv = generateBook(bookDir, raw, fixedimage)
+ return rv
+
+
+if __name__ == '__main__':
+ sys.exit(main(''))
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/getk4pcpids.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/getk4pcpids.py
new file mode 100644
index 0000000..cc8bcd4
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/getk4pcpids.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+#
+# This is a python script. You need a Python interpreter to run it.
+# For example, ActiveState Python, which exists for windows.
+#
+# Changelog
+# 1.00 - Initial version
+# 1.01 - getPidList interface change
+
+__version__ = '1.01'
+
+import sys
+
+class Unbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ def write(self, data):
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+sys.stdout=Unbuffered(sys.stdout)
+
+import os
+import struct
+import binascii
+import kgenpids
+import topazextract
+import mobidedrm
+from alfcrypto import Pukall_Cipher
+
+class DrmException(Exception):
+ pass
+
+def getK4PCpids(path_to_ebook):
+ # Return Kindle4PC PIDs. Assumes that the caller checked that we are not on Linux, which will raise an exception
+
+ mobi = True
+ magic3 = file(path_to_ebook,'rb').read(3)
+ if magic3 == 'TPZ':
+ mobi = False
+
+ if mobi:
+ mb = mobidedrm.MobiBook(path_to_ebook,False)
+ else:
+ mb = topazextract.TopazBook(path_to_ebook)
+
+ md1, md2 = mb.getPIDMetaInfo()
+
+ return kgenpids.getPidList(md1, md2)
+
+
+def main(argv=sys.argv):
+ print ('getk4pcpids.py v%(__version__)s. '
+ 'Copyright 2012 Apprentice Alf' % globals())
+
+ if len(argv)<2 or len(argv)>3:
+ print "Gets the possible book-specific PIDs from K4PC for a particular book"
+ print "Usage:"
+ print " %s []" % sys.argv[0]
+ return 1
+ else:
+ infile = argv[1]
+ try:
+ pidlist = getK4PCpids(infile)
+ except DrmException, e:
+ print "Error: %s" % e
+ return 1
+ pidstring = ','.join(pidlist)
+ print "Possible PIDs are: ", pidstring
+ if len(argv) is 3:
+ outfile = argv[2]
+ file(outfile, 'w').write(pidstring)
+
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignobleepub.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignobleepub.py
new file mode 100644
index 0000000..03aa91f
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignobleepub.py
@@ -0,0 +1,337 @@
+#! /usr/bin/python
+
+from __future__ import with_statement
+
+# ignobleepub.pyw, version 3.5
+
+# To run this program install Python 2.6 from
+# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
+# (make sure to install the version for Python 2.6). Save this script file as
+# ignobleepub.pyw and double-click on it to run it.
+
+# Revision history:
+# 1 - Initial release
+# 2 - Added OS X support by using OpenSSL when available
+# 3 - screen out improper key lengths to prevent segfaults on Linux
+# 3.1 - Allow Windows versions of libcrypto to be found
+# 3.2 - add support for encoding to 'utf-8' when building up list of files to cecrypt from encryption.xml
+# 3.3 - On Windows try PyCrypto first and OpenSSL next
+# 3.4 - Modify interace to allow use with import
+# 3.5 - Fix for potential problem with PyCrypto
+
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import zlib
+import zipfile
+from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
+from contextlib import closing
+import xml.etree.ElementTree as etree
+import Tkinter
+import Tkconstants
+import tkFileDialog
+import tkMessageBox
+
+class IGNOBLEError(Exception):
+ pass
+
+def _load_crypto_libcrypto():
+ from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
+ Structure, c_ulong, create_string_buffer, cast
+ from ctypes.util import find_library
+
+ if sys.platform.startswith('win'):
+ libcrypto = find_library('libeay32')
+ else:
+ libcrypto = find_library('crypto')
+ if libcrypto is None:
+ raise IGNOBLEError('libcrypto not found')
+ libcrypto = CDLL(libcrypto)
+
+ AES_MAXNR = 14
+
+ c_char_pp = POINTER(c_char_p)
+ c_int_p = POINTER(c_int)
+
+ class AES_KEY(Structure):
+ _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
+ ('rounds', c_int)]
+ AES_KEY_p = POINTER(AES_KEY)
+
+ def F(restype, name, argtypes):
+ func = getattr(libcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
+ [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
+ c_int])
+ AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
+ [c_char_p, c_int, AES_KEY_p])
+ AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
+ [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
+ c_int])
+
+ class AES(object):
+ def __init__(self, userkey):
+ self._blocksize = len(userkey)
+ if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+ raise IGNOBLEError('AES improper key used')
+ return
+ key = self._key = AES_KEY()
+ rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
+ if rv < 0:
+ raise IGNOBLEError('Failed to initialize AES key')
+
+ def decrypt(self, data):
+ out = create_string_buffer(len(data))
+ iv = ("\x00" * self._blocksize)
+ rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
+ if rv == 0:
+ raise IGNOBLEError('AES decryption failed')
+ return out.raw
+
+ return AES
+
+def _load_crypto_pycrypto():
+ from Crypto.Cipher import AES as _AES
+
+ class AES(object):
+ def __init__(self, key):
+ self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
+
+ def decrypt(self, data):
+ return self._aes.decrypt(data)
+
+ return AES
+
+def _load_crypto():
+ AES = None
+ cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
+ if sys.platform.startswith('win'):
+ cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
+ for loader in cryptolist:
+ try:
+ AES = loader()
+ break
+ except (ImportError, IGNOBLEError):
+ pass
+ return AES
+
+AES = _load_crypto()
+
+
+
+"""
+Decrypt Barnes & Noble ADEPT encrypted EPUB books.
+"""
+
+
+META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
+NSMAP = {'adept': 'http://ns.adobe.com/adept',
+ 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
+
+class ZipInfo(zipfile.ZipInfo):
+ def __init__(self, *args, **kwargs):
+ if 'compress_type' in kwargs:
+ compress_type = kwargs.pop('compress_type')
+ super(ZipInfo, self).__init__(*args, **kwargs)
+ self.compress_type = compress_type
+
+class Decryptor(object):
+ def __init__(self, bookkey, encryption):
+ enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
+ # self._aes = AES.new(bookkey, AES.MODE_CBC, '\x00'*16)
+ self._aes = AES(bookkey)
+ encryption = etree.fromstring(encryption)
+ self._encrypted = encrypted = set()
+ expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
+ enc('CipherReference'))
+ for elem in encryption.findall(expr):
+ path = elem.get('URI', None)
+ path = path.encode('utf-8')
+ if path is not None:
+ encrypted.add(path)
+
+ def decompress(self, bytes):
+ dc = zlib.decompressobj(-15)
+ bytes = dc.decompress(bytes)
+ ex = dc.decompress('Z') + dc.flush()
+ if ex:
+ bytes = bytes + ex
+ return bytes
+
+ def decrypt(self, path, data):
+ if path in self._encrypted:
+ data = self._aes.decrypt(data)[16:]
+ data = data[:-ord(data[-1])]
+ data = self.decompress(data)
+ return data
+
+
+class DecryptionDialog(Tkinter.Frame):
+ def __init__(self, root):
+ Tkinter.Frame.__init__(self, root, border=5)
+ self.status = Tkinter.Label(self, text='Select files for decryption')
+ self.status.pack(fill=Tkconstants.X, expand=1)
+ body = Tkinter.Frame(self)
+ body.pack(fill=Tkconstants.X, expand=1)
+ sticky = Tkconstants.E + Tkconstants.W
+ body.grid_columnconfigure(1, weight=2)
+ Tkinter.Label(body, text='Key file').grid(row=0)
+ self.keypath = Tkinter.Entry(body, width=30)
+ self.keypath.grid(row=0, column=1, sticky=sticky)
+ if os.path.exists('bnepubkey.b64'):
+ self.keypath.insert(0, 'bnepubkey.b64')
+ button = Tkinter.Button(body, text="...", command=self.get_keypath)
+ button.grid(row=0, column=2)
+ Tkinter.Label(body, text='Input file').grid(row=1)
+ self.inpath = Tkinter.Entry(body, width=30)
+ self.inpath.grid(row=1, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text="...", command=self.get_inpath)
+ button.grid(row=1, column=2)
+ Tkinter.Label(body, text='Output file').grid(row=2)
+ self.outpath = Tkinter.Entry(body, width=30)
+ self.outpath.grid(row=2, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text="...", command=self.get_outpath)
+ button.grid(row=2, column=2)
+ buttons = Tkinter.Frame(self)
+ buttons.pack()
+ botton = Tkinter.Button(
+ buttons, text="Decrypt", width=10, command=self.decrypt)
+ botton.pack(side=Tkconstants.LEFT)
+ Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+ button = Tkinter.Button(
+ buttons, text="Quit", width=10, command=self.quit)
+ button.pack(side=Tkconstants.RIGHT)
+
+ def get_keypath(self):
+ keypath = tkFileDialog.askopenfilename(
+ parent=None, title='Select B&N EPUB key file',
+ defaultextension='.b64',
+ filetypes=[('base64-encoded files', '.b64'),
+ ('All Files', '.*')])
+ if keypath:
+ keypath = os.path.normpath(keypath)
+ self.keypath.delete(0, Tkconstants.END)
+ self.keypath.insert(0, keypath)
+ return
+
+ def get_inpath(self):
+ inpath = tkFileDialog.askopenfilename(
+ parent=None, title='Select B&N-encrypted EPUB file to decrypt',
+ defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
+ ('All files', '.*')])
+ if inpath:
+ inpath = os.path.normpath(inpath)
+ self.inpath.delete(0, Tkconstants.END)
+ self.inpath.insert(0, inpath)
+ return
+
+ def get_outpath(self):
+ outpath = tkFileDialog.asksaveasfilename(
+ parent=None, title='Select unencrypted EPUB file to produce',
+ defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
+ ('All files', '.*')])
+ if outpath:
+ outpath = os.path.normpath(outpath)
+ self.outpath.delete(0, Tkconstants.END)
+ self.outpath.insert(0, outpath)
+ return
+
+ def decrypt(self):
+ keypath = self.keypath.get()
+ inpath = self.inpath.get()
+ outpath = self.outpath.get()
+ if not keypath or not os.path.exists(keypath):
+ self.status['text'] = 'Specified key file does not exist'
+ return
+ if not inpath or not os.path.exists(inpath):
+ self.status['text'] = 'Specified input file does not exist'
+ return
+ if not outpath:
+ self.status['text'] = 'Output file not specified'
+ return
+ if inpath == outpath:
+ self.status['text'] = 'Must have different input and output files'
+ return
+ argv = [sys.argv[0], keypath, inpath, outpath]
+ self.status['text'] = 'Decrypting...'
+ try:
+ cli_main(argv)
+ except Exception, e:
+ self.status['text'] = 'Error: ' + str(e)
+ return
+ self.status['text'] = 'File successfully decrypted'
+
+
+def decryptBook(keypath, inpath, outpath):
+ with open(keypath, 'rb') as f:
+ keyb64 = f.read()
+ key = keyb64.decode('base64')[:16]
+ # aes = AES.new(key, AES.MODE_CBC, '\x00'*16)
+ aes = AES(key)
+
+ with closing(ZipFile(open(inpath, 'rb'))) as inf:
+ namelist = set(inf.namelist())
+ if 'META-INF/rights.xml' not in namelist or \
+ 'META-INF/encryption.xml' not in namelist:
+ raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,))
+ for name in META_NAMES:
+ namelist.remove(name)
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ bookkey = aes.decrypt(bookkey.decode('base64'))
+ bookkey = bookkey[:-ord(bookkey[-1])]
+ encryption = inf.read('META-INF/encryption.xml')
+ decryptor = Decryptor(bookkey[-16:], encryption)
+ kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
+ with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
+ zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
+ outf.writestr(zi, inf.read('mimetype'))
+ for path in namelist:
+ data = inf.read(path)
+ outf.writestr(path, decryptor.decrypt(path, data))
+ return 0
+
+
+def cli_main(argv=sys.argv):
+ progname = os.path.basename(argv[0])
+ if AES is None:
+ print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
+ "separately. Read the top-of-script comment for details." % \
+ (progname,)
+ return 1
+ if len(argv) != 4:
+ print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
+ return 1
+ keypath, inpath, outpath = argv[1:]
+ return decryptBook(keypath, inpath, outpath)
+
+
+def gui_main():
+ root = Tkinter.Tk()
+ if AES is None:
+ root.withdraw()
+ tkMessageBox.showerror(
+ "Ignoble EPUB Decrypter",
+ "This script requires OpenSSL or PyCrypto, which must be installed "
+ "separately. Read the top-of-script comment for details.")
+ return 1
+ root.title('Ignoble EPUB Decrypter')
+ root.resizable(True, False)
+ root.minsize(300, 0)
+ DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
+ root.mainloop()
+ return 0
+
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ sys.exit(cli_main())
+ sys.exit(gui_main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignoblekeygen.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignoblekeygen.py
new file mode 100755
index 0000000..e2c50e2
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ignoblekeygen.py
@@ -0,0 +1,244 @@
+#! /usr/bin/python
+
+from __future__ import with_statement
+
+# ignoblekeygen.pyw, version 2.4
+
+# To run this program install Python 2.6 from
+# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
+# (make sure to install the version for Python 2.6). Save this script file as
+# ignoblekeygen.pyw and double-click on it to run it.
+
+# Revision history:
+# 1 - Initial release
+# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
+# 2.1 - Allow Windows versions of libcrypto to be found
+# 2.2 - On Windows try PyCrypto first and then OpenSSL next
+# 2.3 - Modify interface to allow use of import
+# 2.4 - Improvements to UI and now works in plugins
+
+"""
+Generate Barnes & Noble EPUB user key from name and credit card number.
+"""
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import hashlib
+
+
+
+# use openssl's libcrypt if it exists in place of pycrypto
+# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages
+class IGNOBLEError(Exception):
+ pass
+
+
+def _load_crypto_libcrypto():
+ from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
+ Structure, c_ulong, create_string_buffer, cast
+ from ctypes.util import find_library
+
+ if sys.platform.startswith('win'):
+ libcrypto = find_library('libeay32')
+ else:
+ libcrypto = find_library('crypto')
+ if libcrypto is None:
+ print 'libcrypto not found'
+ raise IGNOBLEError('libcrypto not found')
+ libcrypto = CDLL(libcrypto)
+
+ AES_MAXNR = 14
+
+ c_char_pp = POINTER(c_char_p)
+ c_int_p = POINTER(c_int)
+
+ class AES_KEY(Structure):
+ _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
+ ('rounds', c_int)]
+ AES_KEY_p = POINTER(AES_KEY)
+
+ def F(restype, name, argtypes):
+ func = getattr(libcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
+ [c_char_p, c_int, AES_KEY_p])
+ AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
+ [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
+ c_int])
+ class AES(object):
+ def __init__(self, userkey, iv):
+ self._blocksize = len(userkey)
+ self._iv = iv
+ key = self._key = AES_KEY()
+ rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
+ if rv < 0:
+ raise IGNOBLEError('Failed to initialize AES Encrypt key')
+
+ def encrypt(self, data):
+ out = create_string_buffer(len(data))
+ rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
+ if rv == 0:
+ raise IGNOBLEError('AES encryption failed')
+ return out.raw
+
+ return AES
+
+
+def _load_crypto_pycrypto():
+ from Crypto.Cipher import AES as _AES
+
+ class AES(object):
+ def __init__(self, key, iv):
+ self._aes = _AES.new(key, _AES.MODE_CBC, iv)
+
+ def encrypt(self, data):
+ return self._aes.encrypt(data)
+
+ return AES
+
+def _load_crypto():
+ AES = None
+ cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
+ if sys.platform.startswith('win'):
+ cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
+ for loader in cryptolist:
+ try:
+ AES = loader()
+ break
+ except (ImportError, IGNOBLEError):
+ pass
+ return AES
+
+AES = _load_crypto()
+
+def normalize_name(name):
+ return ''.join(x for x in name.lower() if x != ' ')
+
+
+def generate_keyfile(name, ccn, outpath):
+ # remove spaces and case from name and CC numbers.
+ name = normalize_name(name) + '\x00'
+ ccn = normalize_name(ccn) + '\x00'
+
+ name_sha = hashlib.sha1(name).digest()[:16]
+ ccn_sha = hashlib.sha1(ccn).digest()[:16]
+ both_sha = hashlib.sha1(name + ccn).digest()
+ aes = AES(ccn_sha, name_sha)
+ crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
+ userkey = hashlib.sha1(crypt).digest()
+ with open(outpath, 'wb') as f:
+ f.write(userkey.encode('base64'))
+ return userkey
+
+
+
+
+def cli_main(argv=sys.argv):
+ progname = os.path.basename(argv[0])
+ if AES is None:
+ print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
+ "separately. Read the top-of-script comment for details." % \
+ (progname,)
+ return 1
+ if len(argv) != 4:
+ print "usage: %s NAME CC# OUTFILE" % (progname,)
+ return 1
+ name, ccn, outpath = argv[1:]
+ generate_keyfile(name, ccn, outpath)
+ return 0
+
+
+def gui_main():
+ import Tkinter
+ import Tkconstants
+ import tkFileDialog
+ import tkMessageBox
+
+ class DecryptionDialog(Tkinter.Frame):
+ def __init__(self, root):
+ Tkinter.Frame.__init__(self, root, border=5)
+ self.status = Tkinter.Label(self, text='Enter parameters')
+ self.status.pack(fill=Tkconstants.X, expand=1)
+ body = Tkinter.Frame(self)
+ body.pack(fill=Tkconstants.X, expand=1)
+ sticky = Tkconstants.E + Tkconstants.W
+ body.grid_columnconfigure(1, weight=2)
+ Tkinter.Label(body, text='Account Name').grid(row=0)
+ self.name = Tkinter.Entry(body, width=40)
+ self.name.grid(row=0, column=1, sticky=sticky)
+ Tkinter.Label(body, text='CC#').grid(row=1)
+ self.ccn = Tkinter.Entry(body, width=40)
+ self.ccn.grid(row=1, column=1, sticky=sticky)
+ Tkinter.Label(body, text='Output file').grid(row=2)
+ self.keypath = Tkinter.Entry(body, width=40)
+ self.keypath.grid(row=2, column=1, sticky=sticky)
+ self.keypath.insert(2, 'bnepubkey.b64')
+ button = Tkinter.Button(body, text="...", command=self.get_keypath)
+ button.grid(row=2, column=2)
+ buttons = Tkinter.Frame(self)
+ buttons.pack()
+ botton = Tkinter.Button(
+ buttons, text="Generate", width=10, command=self.generate)
+ botton.pack(side=Tkconstants.LEFT)
+ Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+ button = Tkinter.Button(
+ buttons, text="Quit", width=10, command=self.quit)
+ button.pack(side=Tkconstants.RIGHT)
+
+ def get_keypath(self):
+ keypath = tkFileDialog.asksaveasfilename(
+ parent=None, title='Select B&N EPUB key file to produce',
+ defaultextension='.b64',
+ filetypes=[('base64-encoded files', '.b64'),
+ ('All Files', '.*')])
+ if keypath:
+ keypath = os.path.normpath(keypath)
+ self.keypath.delete(0, Tkconstants.END)
+ self.keypath.insert(0, keypath)
+ return
+
+ def generate(self):
+ name = self.name.get()
+ ccn = self.ccn.get()
+ keypath = self.keypath.get()
+ if not name:
+ self.status['text'] = 'Name not specified'
+ return
+ if not ccn:
+ self.status['text'] = 'Credit card number not specified'
+ return
+ if not keypath:
+ self.status['text'] = 'Output keyfile path not specified'
+ return
+ self.status['text'] = 'Generating...'
+ try:
+ generate_keyfile(name, ccn, keypath)
+ except Exception, e:
+ self.status['text'] = 'Error: ' + str(e)
+ return
+ self.status['text'] = 'Keyfile successfully generated'
+
+ root = Tkinter.Tk()
+ if AES is None:
+ root.withdraw()
+ tkMessageBox.showerror(
+ "Ignoble EPUB Keyfile Generator",
+ "This script requires OpenSSL or PyCrypto, which must be installed "
+ "separately. Read the top-of-script comment for details.")
+ return 1
+ root.title('Ignoble EPUB Keyfile Generator')
+ root.resizable(True, False)
+ root.minsize(300, 0)
+ DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
+ root.mainloop()
+ return 0
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ sys.exit(cli_main())
+ sys.exit(gui_main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptepub.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptepub.py
new file mode 100644
index 0000000..2bb32b1
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptepub.py
@@ -0,0 +1,478 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+# ineptepub.pyw, version 5.6
+# Copyright © 2009-2010 i♥cabbages
+
+# Released under the terms of the GNU General Public Licence, version 3 or
+# later.
+
+# Windows users: Before running this program, you must first install Python 2.6
+# from and PyCrypto from
+# (make sure to
+# install the version for Python 2.6). Save this script file as
+# ineptepub.pyw and double-click on it to run it.
+#
+# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
+# program from the command line (pythonw ineptepub.pyw) or by double-clicking
+# it when it has been associated with PythonLauncher.
+
+# Revision history:
+# 1 - Initial release
+# 2 - Rename to INEPT, fix exit code
+# 5 - Version bump to avoid (?) confusion;
+# Improve OS X support by using OpenSSL when available
+# 5.1 - Improve OpenSSL error checking
+# 5.2 - Fix ctypes error causing segfaults on some systems
+# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
+# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
+# 5.5 - On Windows try PyCrypto first, OpenSSL next
+# 5.6 - Modify interface to allow use with import
+# 5.7 - Fix for potential problem with PyCrypto
+
+"""
+Decrypt Adobe ADEPT-encrypted EPUB books.
+"""
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import zlib
+import zipfile
+from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
+from contextlib import closing
+import xml.etree.ElementTree as etree
+import Tkinter
+import Tkconstants
+import tkFileDialog
+import tkMessageBox
+
+class ADEPTError(Exception):
+ pass
+
+def _load_crypto_libcrypto():
+ from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
+ Structure, c_ulong, create_string_buffer, cast
+ from ctypes.util import find_library
+
+ if sys.platform.startswith('win'):
+ libcrypto = find_library('libeay32')
+ else:
+ libcrypto = find_library('crypto')
+
+ if libcrypto is None:
+ raise ADEPTError('libcrypto not found')
+ libcrypto = CDLL(libcrypto)
+
+ RSA_NO_PADDING = 3
+ AES_MAXNR = 14
+
+ c_char_pp = POINTER(c_char_p)
+ c_int_p = POINTER(c_int)
+
+ class RSA(Structure):
+ pass
+ RSA_p = POINTER(RSA)
+
+ class AES_KEY(Structure):
+ _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
+ ('rounds', c_int)]
+ AES_KEY_p = POINTER(AES_KEY)
+
+ def F(restype, name, argtypes):
+ func = getattr(libcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
+ [RSA_p, c_char_pp, c_long])
+ RSA_size = F(c_int, 'RSA_size', [RSA_p])
+ RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
+ [c_int, c_char_p, c_char_p, RSA_p, c_int])
+ RSA_free = F(None, 'RSA_free', [RSA_p])
+ AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
+ [c_char_p, c_int, AES_KEY_p])
+ AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
+ [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
+ c_int])
+
+ class RSA(object):
+ def __init__(self, der):
+ buf = create_string_buffer(der)
+ pp = c_char_pp(cast(buf, c_char_p))
+ rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
+ if rsa is None:
+ raise ADEPTError('Error parsing ADEPT user key DER')
+
+ def decrypt(self, from_):
+ rsa = self._rsa
+ to = create_string_buffer(RSA_size(rsa))
+ dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
+ RSA_NO_PADDING)
+ if dlen < 0:
+ raise ADEPTError('RSA decryption failed')
+ return to[:dlen]
+
+ def __del__(self):
+ if self._rsa is not None:
+ RSA_free(self._rsa)
+ self._rsa = None
+
+ class AES(object):
+ def __init__(self, userkey):
+ self._blocksize = len(userkey)
+ if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+ raise ADEPTError('AES improper key used')
+ return
+ key = self._key = AES_KEY()
+ rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
+ if rv < 0:
+ raise ADEPTError('Failed to initialize AES key')
+
+ def decrypt(self, data):
+ out = create_string_buffer(len(data))
+ iv = ("\x00" * self._blocksize)
+ rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
+ if rv == 0:
+ raise ADEPTError('AES decryption failed')
+ return out.raw
+
+ return (AES, RSA)
+
+def _load_crypto_pycrypto():
+ from Crypto.Cipher import AES as _AES
+ from Crypto.PublicKey import RSA as _RSA
+
+ # ASN.1 parsing code from tlslite
+ class ASN1Error(Exception):
+ pass
+
+ class ASN1Parser(object):
+ class Parser(object):
+ def __init__(self, bytes):
+ self.bytes = bytes
+ self.index = 0
+
+ def get(self, length):
+ if self.index + length > len(self.bytes):
+ raise ASN1Error("Error decoding ASN.1")
+ x = 0
+ for count in range(length):
+ x <<= 8
+ x |= self.bytes[self.index]
+ self.index += 1
+ return x
+
+ def getFixBytes(self, lengthBytes):
+ bytes = self.bytes[self.index : self.index+lengthBytes]
+ self.index += lengthBytes
+ return bytes
+
+ def getVarBytes(self, lengthLength):
+ lengthBytes = self.get(lengthLength)
+ return self.getFixBytes(lengthBytes)
+
+ def getFixList(self, length, lengthList):
+ l = [0] * lengthList
+ for x in range(lengthList):
+ l[x] = self.get(length)
+ return l
+
+ def getVarList(self, length, lengthLength):
+ lengthList = self.get(lengthLength)
+ if lengthList % length != 0:
+ raise ASN1Error("Error decoding ASN.1")
+ lengthList = int(lengthList/length)
+ l = [0] * lengthList
+ for x in range(lengthList):
+ l[x] = self.get(length)
+ return l
+
+ def startLengthCheck(self, lengthLength):
+ self.lengthCheck = self.get(lengthLength)
+ self.indexCheck = self.index
+
+ def setLengthCheck(self, length):
+ self.lengthCheck = length
+ self.indexCheck = self.index
+
+ def stopLengthCheck(self):
+ if (self.index - self.indexCheck) != self.lengthCheck:
+ raise ASN1Error("Error decoding ASN.1")
+
+ def atLengthCheck(self):
+ if (self.index - self.indexCheck) < self.lengthCheck:
+ return False
+ elif (self.index - self.indexCheck) == self.lengthCheck:
+ return True
+ else:
+ raise ASN1Error("Error decoding ASN.1")
+
+ def __init__(self, bytes):
+ p = self.Parser(bytes)
+ p.get(1)
+ self.length = self._getASN1Length(p)
+ self.value = p.getFixBytes(self.length)
+
+ def getChild(self, which):
+ p = self.Parser(self.value)
+ for x in range(which+1):
+ markIndex = p.index
+ p.get(1)
+ length = self._getASN1Length(p)
+ p.getFixBytes(length)
+ return ASN1Parser(p.bytes[markIndex:p.index])
+
+ def _getASN1Length(self, p):
+ firstLength = p.get(1)
+ if firstLength<=127:
+ return firstLength
+ else:
+ lengthLength = firstLength & 0x7F
+ return p.get(lengthLength)
+
+ class AES(object):
+ def __init__(self, key):
+ self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
+
+ def decrypt(self, data):
+ return self._aes.decrypt(data)
+
+ class RSA(object):
+ def __init__(self, der):
+ key = ASN1Parser([ord(x) for x in der])
+ key = [key.getChild(x).value for x in xrange(1, 4)]
+ key = [self.bytesToNumber(v) for v in key]
+ self._rsa = _RSA.construct(key)
+
+ def bytesToNumber(self, bytes):
+ total = 0L
+ for byte in bytes:
+ total = (total << 8) + byte
+ return total
+
+ def decrypt(self, data):
+ return self._rsa.decrypt(data)
+
+ return (AES, RSA)
+
+def _load_crypto():
+ AES = RSA = None
+ cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
+ if sys.platform.startswith('win'):
+ cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
+ for loader in cryptolist:
+ try:
+ AES, RSA = loader()
+ break
+ except (ImportError, ADEPTError):
+ pass
+ return (AES, RSA)
+AES, RSA = _load_crypto()
+
+META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
+NSMAP = {'adept': 'http://ns.adobe.com/adept',
+ 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
+
+class ZipInfo(zipfile.ZipInfo):
+ def __init__(self, *args, **kwargs):
+ if 'compress_type' in kwargs:
+ compress_type = kwargs.pop('compress_type')
+ super(ZipInfo, self).__init__(*args, **kwargs)
+ self.compress_type = compress_type
+
+class Decryptor(object):
+ def __init__(self, bookkey, encryption):
+ enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
+ self._aes = AES(bookkey)
+ encryption = etree.fromstring(encryption)
+ self._encrypted = encrypted = set()
+ expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
+ enc('CipherReference'))
+ for elem in encryption.findall(expr):
+ path = elem.get('URI', None)
+ if path is not None:
+ path = path.encode('utf-8')
+ encrypted.add(path)
+
+ def decompress(self, bytes):
+ dc = zlib.decompressobj(-15)
+ bytes = dc.decompress(bytes)
+ ex = dc.decompress('Z') + dc.flush()
+ if ex:
+ bytes = bytes + ex
+ return bytes
+
+ def decrypt(self, path, data):
+ if path in self._encrypted:
+ data = self._aes.decrypt(data)[16:]
+ data = data[:-ord(data[-1])]
+ data = self.decompress(data)
+ return data
+
+
+class DecryptionDialog(Tkinter.Frame):
+ def __init__(self, root):
+ Tkinter.Frame.__init__(self, root, border=5)
+ self.status = Tkinter.Label(self, text='Select files for decryption')
+ self.status.pack(fill=Tkconstants.X, expand=1)
+ body = Tkinter.Frame(self)
+ body.pack(fill=Tkconstants.X, expand=1)
+ sticky = Tkconstants.E + Tkconstants.W
+ body.grid_columnconfigure(1, weight=2)
+ Tkinter.Label(body, text='Key file').grid(row=0)
+ self.keypath = Tkinter.Entry(body, width=30)
+ self.keypath.grid(row=0, column=1, sticky=sticky)
+ if os.path.exists('adeptkey.der'):
+ self.keypath.insert(0, 'adeptkey.der')
+ button = Tkinter.Button(body, text="...", command=self.get_keypath)
+ button.grid(row=0, column=2)
+ Tkinter.Label(body, text='Input file').grid(row=1)
+ self.inpath = Tkinter.Entry(body, width=30)
+ self.inpath.grid(row=1, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text="...", command=self.get_inpath)
+ button.grid(row=1, column=2)
+ Tkinter.Label(body, text='Output file').grid(row=2)
+ self.outpath = Tkinter.Entry(body, width=30)
+ self.outpath.grid(row=2, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text="...", command=self.get_outpath)
+ button.grid(row=2, column=2)
+ buttons = Tkinter.Frame(self)
+ buttons.pack()
+ botton = Tkinter.Button(
+ buttons, text="Decrypt", width=10, command=self.decrypt)
+ botton.pack(side=Tkconstants.LEFT)
+ Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+ button = Tkinter.Button(
+ buttons, text="Quit", width=10, command=self.quit)
+ button.pack(side=Tkconstants.RIGHT)
+
+ def get_keypath(self):
+ keypath = tkFileDialog.askopenfilename(
+ parent=None, title='Select ADEPT key file',
+ defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
+ ('All Files', '.*')])
+ if keypath:
+ keypath = os.path.normpath(keypath)
+ self.keypath.delete(0, Tkconstants.END)
+ self.keypath.insert(0, keypath)
+ return
+
+ def get_inpath(self):
+ inpath = tkFileDialog.askopenfilename(
+ parent=None, title='Select ADEPT-encrypted EPUB file to decrypt',
+ defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
+ ('All files', '.*')])
+ if inpath:
+ inpath = os.path.normpath(inpath)
+ self.inpath.delete(0, Tkconstants.END)
+ self.inpath.insert(0, inpath)
+ return
+
+ def get_outpath(self):
+ outpath = tkFileDialog.asksaveasfilename(
+ parent=None, title='Select unencrypted EPUB file to produce',
+ defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
+ ('All files', '.*')])
+ if outpath:
+ outpath = os.path.normpath(outpath)
+ self.outpath.delete(0, Tkconstants.END)
+ self.outpath.insert(0, outpath)
+ return
+
+ def decrypt(self):
+ keypath = self.keypath.get()
+ inpath = self.inpath.get()
+ outpath = self.outpath.get()
+ if not keypath or not os.path.exists(keypath):
+ self.status['text'] = 'Specified key file does not exist'
+ return
+ if not inpath or not os.path.exists(inpath):
+ self.status['text'] = 'Specified input file does not exist'
+ return
+ if not outpath:
+ self.status['text'] = 'Output file not specified'
+ return
+ if inpath == outpath:
+ self.status['text'] = 'Must have different input and output files'
+ return
+ argv = [sys.argv[0], keypath, inpath, outpath]
+ self.status['text'] = 'Decrypting...'
+ try:
+ cli_main(argv)
+ except Exception, e:
+ self.status['text'] = 'Error: ' + str(e)
+ return
+ self.status['text'] = 'File successfully decrypted'
+
+
+def decryptBook(keypath, inpath, outpath):
+ with open(keypath, 'rb') as f:
+ keyder = f.read()
+ rsa = RSA(keyder)
+ with closing(ZipFile(open(inpath, 'rb'))) as inf:
+ namelist = set(inf.namelist())
+ if 'META-INF/rights.xml' not in namelist or \
+ 'META-INF/encryption.xml' not in namelist:
+ raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,))
+ for name in META_NAMES:
+ namelist.remove(name)
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ bookkey = rsa.decrypt(bookkey.decode('base64'))
+ # Padded as per RSAES-PKCS1-v1_5
+ if bookkey[-17] != '\x00':
+ raise ADEPTError('problem decrypting session key')
+ encryption = inf.read('META-INF/encryption.xml')
+ decryptor = Decryptor(bookkey[-16:], encryption)
+ kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
+ with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
+ zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
+ outf.writestr(zi, inf.read('mimetype'))
+ for path in namelist:
+ data = inf.read(path)
+ outf.writestr(path, decryptor.decrypt(path, data))
+ return 0
+
+
+def cli_main(argv=sys.argv):
+ progname = os.path.basename(argv[0])
+ if AES is None:
+ print "%s: This script requires OpenSSL or PyCrypto, which must be" \
+ " installed separately. Read the top-of-script comment for" \
+ " details." % (progname,)
+ return 1
+ if len(argv) != 4:
+ print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
+ return 1
+ keypath, inpath, outpath = argv[1:]
+ return decryptBook(keypath, inpath, outpath)
+
+
+def gui_main():
+ root = Tkinter.Tk()
+ if AES is None:
+ root.withdraw()
+ tkMessageBox.showerror(
+ "INEPT EPUB Decrypter",
+ "This script requires OpenSSL or PyCrypto, which must be"
+ " installed separately. Read the top-of-script comment for"
+ " details.")
+ return 1
+ root.title('INEPT EPUB Decrypter')
+ root.resizable(True, False)
+ root.minsize(300, 0)
+ DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
+ root.mainloop()
+ return 0
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ sys.exit(cli_main())
+ sys.exit(gui_main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptkey.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptkey.py
new file mode 100755
index 0000000..723b7c6
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptkey.py
@@ -0,0 +1,457 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+# ineptkey.pyw, version 5.6
+# Copyright © 2009-2010 i♥cabbages
+
+# Released under the terms of the GNU General Public Licence, version 3 or
+# later.
+
+# Windows users: Before running this program, you must first install Python 2.6
+# from and PyCrypto from
+# (make certain
+# to install the version for Python 2.6). Then save this script file as
+# ineptkey.pyw and double-click on it to run it. It will create a file named
+# adeptkey.der in the same directory. This is your ADEPT user key.
+#
+# Mac OS X users: Save this script file as ineptkey.pyw. You can run this
+# program from the command line (pythonw ineptkey.pyw) or by double-clicking
+# it when it has been associated with PythonLauncher. It will create a file
+# named adeptkey.der in the same directory. This is your ADEPT user key.
+
+# Revision history:
+# 1 - Initial release, for Adobe Digital Editions 1.7
+# 2 - Better algorithm for finding pLK; improved error handling
+# 3 - Rename to INEPT
+# 4 - Series of changes by joblack (and others?) --
+# 4.1 - quick beta fix for ADE 1.7.2 (anon)
+# 4.2 - added old 1.7.1 processing
+# 4.3 - better key search
+# 4.4 - Make it working on 64-bit Python
+# 5 - Clean up and improve 4.x changes;
+# Clean up and merge OS X support by unknown
+# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto
+# 5.2 - added support for output of key to a particular file
+# 5.3 - On Windows try PyCrypto first, OpenSSL next
+# 5.4 - Modify interface to allow use of import
+# 5.5 - Fix for potential problem with PyCrypto
+# 5.6 - Revise to allow use in Plugins to eliminate need for duplicate code
+
+"""
+Retrieve Adobe ADEPT user key.
+"""
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import struct
+
+try:
+ from calibre.constants import iswindows, isosx
+except:
+ iswindows = sys.platform.startswith('win')
+ isosx = sys.platform.startswith('darwin')
+
+class ADEPTError(Exception):
+ pass
+
+if iswindows:
+ from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
+ create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
+ string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \
+ c_long, c_ulong
+
+ from ctypes.wintypes import LPVOID, DWORD, BOOL
+ import _winreg as winreg
+
+ def _load_crypto_libcrypto():
+ from ctypes.util import find_library
+ libcrypto = find_library('libeay32')
+ if libcrypto is None:
+ raise ADEPTError('libcrypto not found')
+ libcrypto = CDLL(libcrypto)
+ AES_MAXNR = 14
+ c_char_pp = POINTER(c_char_p)
+ c_int_p = POINTER(c_int)
+ class AES_KEY(Structure):
+ _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
+ ('rounds', c_int)]
+ AES_KEY_p = POINTER(AES_KEY)
+
+ def F(restype, name, argtypes):
+ func = getattr(libcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
+ [c_char_p, c_int, AES_KEY_p])
+ AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
+ [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
+ c_int])
+ class AES(object):
+ def __init__(self, userkey):
+ self._blocksize = len(userkey)
+ if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+ raise ADEPTError('AES improper key used')
+ key = self._key = AES_KEY()
+ rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
+ if rv < 0:
+ raise ADEPTError('Failed to initialize AES key')
+ def decrypt(self, data):
+ out = create_string_buffer(len(data))
+ iv = ("\x00" * self._blocksize)
+ rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
+ if rv == 0:
+ raise ADEPTError('AES decryption failed')
+ return out.raw
+ return AES
+
+ def _load_crypto_pycrypto():
+ from Crypto.Cipher import AES as _AES
+ class AES(object):
+ def __init__(self, key):
+ self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
+ def decrypt(self, data):
+ return self._aes.decrypt(data)
+ return AES
+
+ def _load_crypto():
+ AES = None
+ for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
+ try:
+ AES = loader()
+ break
+ except (ImportError, ADEPTError):
+ pass
+ return AES
+
+ AES = _load_crypto()
+
+
+ DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
+ PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
+
+ MAX_PATH = 255
+
+ kernel32 = windll.kernel32
+ advapi32 = windll.advapi32
+ crypt32 = windll.crypt32
+
+ def GetSystemDirectory():
+ GetSystemDirectoryW = kernel32.GetSystemDirectoryW
+ GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
+ GetSystemDirectoryW.restype = c_uint
+ def GetSystemDirectory():
+ buffer = create_unicode_buffer(MAX_PATH + 1)
+ GetSystemDirectoryW(buffer, len(buffer))
+ return buffer.value
+ return GetSystemDirectory
+ GetSystemDirectory = GetSystemDirectory()
+
+ def GetVolumeSerialNumber():
+ GetVolumeInformationW = kernel32.GetVolumeInformationW
+ GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
+ POINTER(c_uint), POINTER(c_uint),
+ POINTER(c_uint), c_wchar_p, c_uint]
+ GetVolumeInformationW.restype = c_uint
+ def GetVolumeSerialNumber(path):
+ vsn = c_uint(0)
+ GetVolumeInformationW(
+ path, None, 0, byref(vsn), None, None, None, 0)
+ return vsn.value
+ return GetVolumeSerialNumber
+ GetVolumeSerialNumber = GetVolumeSerialNumber()
+
+ def GetUserName():
+ GetUserNameW = advapi32.GetUserNameW
+ GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
+ GetUserNameW.restype = c_uint
+ def GetUserName():
+ buffer = create_unicode_buffer(32)
+ size = c_uint(len(buffer))
+ while not GetUserNameW(buffer, byref(size)):
+ buffer = create_unicode_buffer(len(buffer) * 2)
+ size.value = len(buffer)
+ return buffer.value.encode('utf-16-le')[::2]
+ return GetUserName
+ GetUserName = GetUserName()
+
+ PAGE_EXECUTE_READWRITE = 0x40
+ MEM_COMMIT = 0x1000
+ MEM_RESERVE = 0x2000
+
+ def VirtualAlloc():
+ _VirtualAlloc = kernel32.VirtualAlloc
+ _VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD]
+ _VirtualAlloc.restype = LPVOID
+ def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE),
+ protect=PAGE_EXECUTE_READWRITE):
+ return _VirtualAlloc(addr, size, alloctype, protect)
+ return VirtualAlloc
+ VirtualAlloc = VirtualAlloc()
+
+ MEM_RELEASE = 0x8000
+
+ def VirtualFree():
+ _VirtualFree = kernel32.VirtualFree
+ _VirtualFree.argtypes = [LPVOID, c_size_t, DWORD]
+ _VirtualFree.restype = BOOL
+ def VirtualFree(addr, size=0, freetype=MEM_RELEASE):
+ return _VirtualFree(addr, size, freetype)
+ return VirtualFree
+ VirtualFree = VirtualFree()
+
+ class NativeFunction(object):
+ def __init__(self, restype, argtypes, insns):
+ self._buf = buf = VirtualAlloc(None, len(insns))
+ memmove(buf, insns, len(insns))
+ ftype = CFUNCTYPE(restype, *argtypes)
+ self._native = ftype(buf)
+
+ def __call__(self, *args):
+ return self._native(*args)
+
+ def __del__(self):
+ if self._buf is not None:
+ VirtualFree(self._buf)
+ self._buf = None
+
+ if struct.calcsize("P") == 4:
+ CPUID0_INSNS = (
+ "\x53" # push %ebx
+ "\x31\xc0" # xor %eax,%eax
+ "\x0f\xa2" # cpuid
+ "\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
+ "\x89\x18" # mov %ebx,0x0(%eax)
+ "\x89\x50\x04" # mov %edx,0x4(%eax)
+ "\x89\x48\x08" # mov %ecx,0x8(%eax)
+ "\x5b" # pop %ebx
+ "\xc3" # ret
+ )
+ CPUID1_INSNS = (
+ "\x53" # push %ebx
+ "\x31\xc0" # xor %eax,%eax
+ "\x40" # inc %eax
+ "\x0f\xa2" # cpuid
+ "\x5b" # pop %ebx
+ "\xc3" # ret
+ )
+ else:
+ CPUID0_INSNS = (
+ "\x49\x89\xd8" # mov %rbx,%r8
+ "\x49\x89\xc9" # mov %rcx,%r9
+ "\x48\x31\xc0" # xor %rax,%rax
+ "\x0f\xa2" # cpuid
+ "\x4c\x89\xc8" # mov %r9,%rax
+ "\x89\x18" # mov %ebx,0x0(%rax)
+ "\x89\x50\x04" # mov %edx,0x4(%rax)
+ "\x89\x48\x08" # mov %ecx,0x8(%rax)
+ "\x4c\x89\xc3" # mov %r8,%rbx
+ "\xc3" # retq
+ )
+ CPUID1_INSNS = (
+ "\x53" # push %rbx
+ "\x48\x31\xc0" # xor %rax,%rax
+ "\x48\xff\xc0" # inc %rax
+ "\x0f\xa2" # cpuid
+ "\x5b" # pop %rbx
+ "\xc3" # retq
+ )
+
+ def cpuid0():
+ _cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
+ buf = create_string_buffer(12)
+ def cpuid0():
+ _cpuid0(buf)
+ return buf.raw
+ return cpuid0
+ cpuid0 = cpuid0()
+
+ cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS)
+
+ class DataBlob(Structure):
+ _fields_ = [('cbData', c_uint),
+ ('pbData', c_void_p)]
+ DataBlob_p = POINTER(DataBlob)
+
+ def CryptUnprotectData():
+ _CryptUnprotectData = crypt32.CryptUnprotectData
+ _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
+ c_void_p, c_void_p, c_uint, DataBlob_p]
+ _CryptUnprotectData.restype = c_uint
+ def CryptUnprotectData(indata, entropy):
+ indatab = create_string_buffer(indata)
+ indata = DataBlob(len(indata), cast(indatab, c_void_p))
+ entropyb = create_string_buffer(entropy)
+ entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
+ outdata = DataBlob()
+ if not _CryptUnprotectData(byref(indata), None, byref(entropy),
+ None, None, 0, byref(outdata)):
+ raise ADEPTError("Failed to decrypt user key key (sic)")
+ return string_at(outdata.pbData, outdata.cbData)
+ return CryptUnprotectData
+ CryptUnprotectData = CryptUnprotectData()
+
+ def retrieve_keys():
+ if AES is None:
+ raise ADEPTError("PyCrypto or OpenSSL must be installed")
+ root = GetSystemDirectory().split('\\')[0] + '\\'
+ serial = GetVolumeSerialNumber(root)
+ vendor = cpuid0()
+ signature = struct.pack('>I', cpuid1())[1:]
+ user = GetUserName()
+ entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
+ cuser = winreg.HKEY_CURRENT_USER
+ try:
+ regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
+ except WindowsError:
+ raise ADEPTError("Adobe Digital Editions not activated")
+ device = winreg.QueryValueEx(regkey, 'key')[0]
+ keykey = CryptUnprotectData(device, entropy)
+ userkey = None
+ keys = []
+ try:
+ plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
+ except WindowsError:
+ raise ADEPTError("Could not locate ADE activation")
+ for i in xrange(0, 16):
+ try:
+ plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
+ except WindowsError:
+ break
+ ktype = winreg.QueryValueEx(plkparent, None)[0]
+ if ktype != 'credentials':
+ continue
+ for j in xrange(0, 16):
+ try:
+ plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
+ except WindowsError:
+ break
+ ktype = winreg.QueryValueEx(plkkey, None)[0]
+ if ktype != 'privateLicenseKey':
+ continue
+ userkey = winreg.QueryValueEx(plkkey, 'value')[0]
+ userkey = userkey.decode('base64')
+ aes = AES(keykey)
+ userkey = aes.decrypt(userkey)
+ userkey = userkey[26:-ord(userkey[-1])]
+ keys.append(userkey)
+ if len(keys) == 0:
+ raise ADEPTError('Could not locate privateLicenseKey')
+ return keys
+
+
+elif isosx:
+ import xml.etree.ElementTree as etree
+ import subprocess
+
+ NSMAP = {'adept': 'http://ns.adobe.com/adept',
+ 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
+
+ def findActivationDat():
+ home = os.getenv('HOME')
+ cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
+ cmdline = cmdline.encode(sys.getfilesystemencoding())
+ p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+ out1, out2 = p2.communicate()
+ reslst = out1.split('\n')
+ cnt = len(reslst)
+ for j in xrange(cnt):
+ resline = reslst[j]
+ pp = resline.find('activation.dat')
+ if pp >= 0:
+ ActDatPath = resline
+ break
+ if os.path.exists(ActDatPath):
+ return ActDatPath
+ return None
+
+ def retrieve_keys():
+ actpath = findActivationDat()
+ if actpath is None:
+ raise ADEPTError("Could not locate ADE activation")
+ tree = etree.parse(actpath)
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey'))
+ userkey = tree.findtext(expr)
+ userkey = userkey.decode('base64')
+ userkey = userkey[26:]
+ return [userkey]
+
+else:
+ def retrieve_keys(keypath):
+ raise ADEPTError("This script only supports Windows and Mac OS X.")
+ return []
+
+def retrieve_key(keypath):
+ keys = retrieve_keys()
+ with open(keypath, 'wb') as f:
+ f.write(keys[0])
+ return True
+
+def extractKeyfile(keypath):
+ try:
+ success = retrieve_key(keypath)
+ except ADEPTError, e:
+ print "Key generation Error: " + str(e)
+ return 1
+ except Exception, e:
+ print "General Error: " + str(e)
+ return 1
+ if not success:
+ return 1
+ return 0
+
+
+def cli_main(argv=sys.argv):
+ keypath = argv[1]
+ return extractKeyfile(keypath)
+
+
+def main(argv=sys.argv):
+ import Tkinter
+ import Tkconstants
+ import tkMessageBox
+ import traceback
+
+ class ExceptionDialog(Tkinter.Frame):
+ def __init__(self, root, text):
+ Tkinter.Frame.__init__(self, root, border=5)
+ label = Tkinter.Label(self, text="Unexpected error:",
+ anchor=Tkconstants.W, justify=Tkconstants.LEFT)
+ label.pack(fill=Tkconstants.X, expand=0)
+ self.text = Tkinter.Text(self)
+ self.text.pack(fill=Tkconstants.BOTH, expand=1)
+
+ self.text.insert(Tkconstants.END, text)
+
+
+ root = Tkinter.Tk()
+ root.withdraw()
+ progname = os.path.basename(argv[0])
+ keypath = os.path.abspath("adeptkey.der")
+ success = False
+ try:
+ success = retrieve_key(keypath)
+ except ADEPTError, e:
+ tkMessageBox.showerror("ADEPT Key", "Error: " + str(e))
+ except Exception:
+ root.wm_state('normal')
+ root.title('ADEPT Key')
+ text = traceback.format_exc()
+ ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
+ root.mainloop()
+ if not success:
+ return 1
+ tkMessageBox.showinfo(
+ "ADEPT Key", "Key successfully retrieved to %s" % (keypath))
+ return 0
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ sys.exit(cli_main())
+ sys.exit(main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptpdf.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptpdf.py
new file mode 100755
index 0000000..20721d1
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/ineptpdf.py
@@ -0,0 +1,2255 @@
+#! /usr/bin/env python
+# ineptpdf.pyw, version 7.11
+
+from __future__ import with_statement
+
+# To run this program install Python 2.6 from http://www.python.org/download/
+# and OpenSSL (already installed on Mac OS X and Linux) OR
+# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
+# (make sure to install the version for Python 2.6). Save this script file as
+# ineptpdf.pyw and double-click on it to run it.
+
+# Revision history:
+# 1 - Initial release
+# 2 - Improved determination of key-generation algorithm
+# 3 - Correctly handle PDF >=1.5 cross-reference streams
+# 4 - Removal of ciando's personal ID
+# 5 - Automated decryption of a complete directory
+# 6.1 - backward compatibility for 1.7.1 and old adeptkey.der
+# 7 - Get cross reference streams and object streams working for input.
+# Not yet supported on output but this only effects file size,
+# not functionality. (anon2)
+# 7.1 - Correct a problem when an old trailer is not followed by startxref
+# 7.2 - Correct malformed Mac OS resource forks for Stanza (anon2)
+# - Support for cross ref streams on output (decreases file size)
+# 7.3 - Correct bug in trailer with cross ref stream that caused the error
+# "The root object is missing or invalid" in Adobe Reader. (anon2)
+# 7.4 - Force all generation numbers in output file to be 0, like in v6.
+# Fallback code for wrong xref improved (search till last trailer
+# instead of first) (anon2)
+# 7.5 - allow support for OpenSSL to replace pycrypto on all platforms
+# implemented ARC4 interface to OpenSSL
+# fixed minor typos
+# 7.6 - backported AES and other fixes from version 8.4.48
+# 7.7 - On Windows try PyCrypto first and OpenSSL next
+# 7.8 - Modify interface to allow use of import
+# 7.9 - Bug fix for some session key errors when len(bookkey) > length required
+# 7.10 - Various tweaks to fix minor problems.
+# 7.11 - More tweaks to fix minor problems.
+
+"""
+Decrypts Adobe ADEPT-encrypted PDF files.
+"""
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import re
+import zlib
+import struct
+import hashlib
+from itertools import chain, islice
+import xml.etree.ElementTree as etree
+import Tkinter
+import Tkconstants
+import tkFileDialog
+import tkMessageBox
+
+class ADEPTError(Exception):
+ pass
+
+
+import hashlib
+
+def SHA256(message):
+ ctx = hashlib.sha256()
+ ctx.update(message)
+ return ctx.digest()
+
+
+def _load_crypto_libcrypto():
+ from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
+ Structure, c_ulong, create_string_buffer, cast
+ from ctypes.util import find_library
+
+ if sys.platform.startswith('win'):
+ libcrypto = find_library('libeay32')
+ else:
+ libcrypto = find_library('crypto')
+
+ if libcrypto is None:
+ raise ADEPTError('libcrypto not found')
+ libcrypto = CDLL(libcrypto)
+
+ AES_MAXNR = 14
+
+ RSA_NO_PADDING = 3
+
+ c_char_pp = POINTER(c_char_p)
+ c_int_p = POINTER(c_int)
+
+ class AES_KEY(Structure):
+ _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
+ AES_KEY_p = POINTER(AES_KEY)
+
+ class RC4_KEY(Structure):
+ _fields_ = [('x', c_int), ('y', c_int), ('box', c_int * 256)]
+ RC4_KEY_p = POINTER(RC4_KEY)
+
+ class RSA(Structure):
+ pass
+ RSA_p = POINTER(RSA)
+
+ def F(restype, name, argtypes):
+ func = getattr(libcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
+ AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
+
+ RC4_set_key = F(None,'RC4_set_key',[RC4_KEY_p, c_int, c_char_p])
+ RC4_crypt = F(None,'RC4',[RC4_KEY_p, c_int, c_char_p, c_char_p])
+
+ d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
+ [RSA_p, c_char_pp, c_long])
+ RSA_size = F(c_int, 'RSA_size', [RSA_p])
+ RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
+ [c_int, c_char_p, c_char_p, RSA_p, c_int])
+ RSA_free = F(None, 'RSA_free', [RSA_p])
+
+ class RSA(object):
+ def __init__(self, der):
+ buf = create_string_buffer(der)
+ pp = c_char_pp(cast(buf, c_char_p))
+ rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
+ if rsa is None:
+ raise ADEPTError('Error parsing ADEPT user key DER')
+
+ def decrypt(self, from_):
+ rsa = self._rsa
+ to = create_string_buffer(RSA_size(rsa))
+ dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
+ RSA_NO_PADDING)
+ if dlen < 0:
+ raise ADEPTError('RSA decryption failed')
+ return to[1:dlen]
+
+ def __del__(self):
+ if self._rsa is not None:
+ RSA_free(self._rsa)
+ self._rsa = None
+
+ class ARC4(object):
+ @classmethod
+ def new(cls, userkey):
+ self = ARC4()
+ self._blocksize = len(userkey)
+ key = self._key = RC4_KEY()
+ RC4_set_key(key, self._blocksize, userkey)
+ return self
+ def __init__(self):
+ self._blocksize = 0
+ self._key = None
+ def decrypt(self, data):
+ out = create_string_buffer(len(data))
+ RC4_crypt(self._key, len(data), data, out)
+ return out.raw
+
+ class AES(object):
+ MODE_CBC = 0
+ @classmethod
+ def new(cls, userkey, mode, iv):
+ self = AES()
+ self._blocksize = len(userkey)
+ # mode is ignored since CBCMODE is only thing supported/used so far
+ self._mode = mode
+ if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+ raise ADEPTError('AES improper key used')
+ return
+ keyctx = self._keyctx = AES_KEY()
+ self._iv = iv
+ rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
+ if rv < 0:
+ raise ADEPTError('Failed to initialize AES key')
+ return self
+ def __init__(self):
+ self._blocksize = 0
+ self._keyctx = None
+ self._iv = 0
+ self._mode = 0
+ def decrypt(self, data):
+ out = create_string_buffer(len(data))
+ rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self._iv, 0)
+ if rv == 0:
+ raise ADEPTError('AES decryption failed')
+ return out.raw
+
+ return (ARC4, RSA, AES)
+
+
+def _load_crypto_pycrypto():
+ from Crypto.PublicKey import RSA as _RSA
+ from Crypto.Cipher import ARC4 as _ARC4
+ from Crypto.Cipher import AES as _AES
+
+ # ASN.1 parsing code from tlslite
+ class ASN1Error(Exception):
+ pass
+
+ class ASN1Parser(object):
+ class Parser(object):
+ def __init__(self, bytes):
+ self.bytes = bytes
+ self.index = 0
+
+ def get(self, length):
+ if self.index + length > len(self.bytes):
+ raise ASN1Error("Error decoding ASN.1")
+ x = 0
+ for count in range(length):
+ x <<= 8
+ x |= self.bytes[self.index]
+ self.index += 1
+ return x
+
+ def getFixBytes(self, lengthBytes):
+ bytes = self.bytes[self.index : self.index+lengthBytes]
+ self.index += lengthBytes
+ return bytes
+
+ def getVarBytes(self, lengthLength):
+ lengthBytes = self.get(lengthLength)
+ return self.getFixBytes(lengthBytes)
+
+ def getFixList(self, length, lengthList):
+ l = [0] * lengthList
+ for x in range(lengthList):
+ l[x] = self.get(length)
+ return l
+
+ def getVarList(self, length, lengthLength):
+ lengthList = self.get(lengthLength)
+ if lengthList % length != 0:
+ raise ASN1Error("Error decoding ASN.1")
+ lengthList = int(lengthList/length)
+ l = [0] * lengthList
+ for x in range(lengthList):
+ l[x] = self.get(length)
+ return l
+
+ def startLengthCheck(self, lengthLength):
+ self.lengthCheck = self.get(lengthLength)
+ self.indexCheck = self.index
+
+ def setLengthCheck(self, length):
+ self.lengthCheck = length
+ self.indexCheck = self.index
+
+ def stopLengthCheck(self):
+ if (self.index - self.indexCheck) != self.lengthCheck:
+ raise ASN1Error("Error decoding ASN.1")
+
+ def atLengthCheck(self):
+ if (self.index - self.indexCheck) < self.lengthCheck:
+ return False
+ elif (self.index - self.indexCheck) == self.lengthCheck:
+ return True
+ else:
+ raise ASN1Error("Error decoding ASN.1")
+
+ def __init__(self, bytes):
+ p = self.Parser(bytes)
+ p.get(1)
+ self.length = self._getASN1Length(p)
+ self.value = p.getFixBytes(self.length)
+
+ def getChild(self, which):
+ p = self.Parser(self.value)
+ for x in range(which+1):
+ markIndex = p.index
+ p.get(1)
+ length = self._getASN1Length(p)
+ p.getFixBytes(length)
+ return ASN1Parser(p.bytes[markIndex:p.index])
+
+ def _getASN1Length(self, p):
+ firstLength = p.get(1)
+ if firstLength<=127:
+ return firstLength
+ else:
+ lengthLength = firstLength & 0x7F
+ return p.get(lengthLength)
+
+ class ARC4(object):
+ @classmethod
+ def new(cls, userkey):
+ self = ARC4()
+ self._arc4 = _ARC4.new(userkey)
+ return self
+ def __init__(self):
+ self._arc4 = None
+ def decrypt(self, data):
+ return self._arc4.decrypt(data)
+
+ class AES(object):
+ MODE_CBC = _AES.MODE_CBC
+ @classmethod
+ def new(cls, userkey, mode, iv):
+ self = AES()
+ self._aes = _AES.new(userkey, mode, iv)
+ return self
+ def __init__(self):
+ self._aes = None
+ def decrypt(self, data):
+ return self._aes.decrypt(data)
+
+ class RSA(object):
+ def __init__(self, der):
+ key = ASN1Parser([ord(x) for x in der])
+ key = [key.getChild(x).value for x in xrange(1, 4)]
+ key = [self.bytesToNumber(v) for v in key]
+ self._rsa = _RSA.construct(key)
+
+ def bytesToNumber(self, bytes):
+ total = 0L
+ for byte in bytes:
+ total = (total << 8) + byte
+ return total
+
+ def decrypt(self, data):
+ return self._rsa.decrypt(data)
+
+ return (ARC4, RSA, AES)
+
+def _load_crypto():
+ ARC4 = RSA = AES = None
+ cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
+ if sys.platform.startswith('win'):
+ cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
+ for loader in cryptolist:
+ try:
+ ARC4, RSA, AES = loader()
+ break
+ except (ImportError, ADEPTError):
+ pass
+ return (ARC4, RSA, AES)
+ARC4, RSA, AES = _load_crypto()
+
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+# Do we generate cross reference streams on output?
+# 0 = never
+# 1 = only if present in input
+# 2 = always
+
+GEN_XREF_STM = 1
+
+# This is the value for the current document
+gen_xref_stm = False # will be set in PDFSerializer
+
+# PDF parsing routines from pdfminer, with changes for EBX_HANDLER
+
+# Utilities
+
+def choplist(n, seq):
+ '''Groups every n elements of the list.'''
+ r = []
+ for x in seq:
+ r.append(x)
+ if len(r) == n:
+ yield tuple(r)
+ r = []
+ return
+
+def nunpack(s, default=0):
+ '''Unpacks up to 4 bytes big endian.'''
+ l = len(s)
+ if not l:
+ return default
+ elif l == 1:
+ return ord(s)
+ elif l == 2:
+ return struct.unpack('>H', s)[0]
+ elif l == 3:
+ return struct.unpack('>L', '\x00'+s)[0]
+ elif l == 4:
+ return struct.unpack('>L', s)[0]
+ else:
+ return TypeError('invalid length: %d' % l)
+
+
+STRICT = 0
+
+
+# PS Exceptions
+
+class PSException(Exception): pass
+class PSEOF(PSException): pass
+class PSSyntaxError(PSException): pass
+class PSTypeError(PSException): pass
+class PSValueError(PSException): pass
+
+
+# Basic PostScript Types
+
+
+# PSLiteral
+class PSObject(object): pass
+
+class PSLiteral(PSObject):
+ '''
+ PS literals (e.g. "/Name").
+ Caution: Never create these objects directly.
+ Use PSLiteralTable.intern() instead.
+ '''
+ def __init__(self, name):
+ self.name = name
+ return
+
+ def __repr__(self):
+ name = []
+ for char in self.name:
+ if not char.isalnum():
+ char = '#%02x' % ord(char)
+ name.append(char)
+ return '/%s' % ''.join(name)
+
+# PSKeyword
+class PSKeyword(PSObject):
+ '''
+ PS keywords (e.g. "showpage").
+ Caution: Never create these objects directly.
+ Use PSKeywordTable.intern() instead.
+ '''
+ def __init__(self, name):
+ self.name = name
+ return
+
+ def __repr__(self):
+ return self.name
+
+# PSSymbolTable
+class PSSymbolTable(object):
+
+ '''
+ Symbol table that stores PSLiteral or PSKeyword.
+ '''
+
+ def __init__(self, classe):
+ self.dic = {}
+ self.classe = classe
+ return
+
+ def intern(self, name):
+ if name in self.dic:
+ lit = self.dic[name]
+ else:
+ lit = self.classe(name)
+ self.dic[name] = lit
+ return lit
+
+PSLiteralTable = PSSymbolTable(PSLiteral)
+PSKeywordTable = PSSymbolTable(PSKeyword)
+LIT = PSLiteralTable.intern
+KWD = PSKeywordTable.intern
+KEYWORD_BRACE_BEGIN = KWD('{')
+KEYWORD_BRACE_END = KWD('}')
+KEYWORD_ARRAY_BEGIN = KWD('[')
+KEYWORD_ARRAY_END = KWD(']')
+KEYWORD_DICT_BEGIN = KWD('<<')
+KEYWORD_DICT_END = KWD('>>')
+
+
+def literal_name(x):
+ if not isinstance(x, PSLiteral):
+ if STRICT:
+ raise PSTypeError('Literal required: %r' % x)
+ else:
+ return str(x)
+ return x.name
+
+def keyword_name(x):
+ if not isinstance(x, PSKeyword):
+ if STRICT:
+ raise PSTypeError('Keyword required: %r' % x)
+ else:
+ return str(x)
+ return x.name
+
+
+## PSBaseParser
+##
+EOL = re.compile(r'[\r\n]')
+SPC = re.compile(r'\s')
+NONSPC = re.compile(r'\S')
+HEX = re.compile(r'[0-9a-fA-F]')
+END_LITERAL = re.compile(r'[#/%\[\]()<>{}\s]')
+END_HEX_STRING = re.compile(r'[^\s0-9a-fA-F]')
+HEX_PAIR = re.compile(r'[0-9a-fA-F]{2}|.')
+END_NUMBER = re.compile(r'[^0-9]')
+END_KEYWORD = re.compile(r'[#/%\[\]()<>{}\s]')
+END_STRING = re.compile(r'[()\134]')
+OCT_STRING = re.compile(r'[0-7]')
+ESC_STRING = { 'b':8, 't':9, 'n':10, 'f':12, 'r':13, '(':40, ')':41, '\\':92 }
+
+class PSBaseParser(object):
+
+ '''
+ Most basic PostScript parser that performs only basic tokenization.
+ '''
+ BUFSIZ = 4096
+
+ def __init__(self, fp):
+ self.fp = fp
+ self.seek(0)
+ return
+
+ def __repr__(self):
+ return '' % (self.fp, self.bufpos)
+
+ def flush(self):
+ return
+
+ def close(self):
+ self.flush()
+ return
+
+ def tell(self):
+ return self.bufpos+self.charpos
+
+ def poll(self, pos=None, n=80):
+ pos0 = self.fp.tell()
+ if not pos:
+ pos = self.bufpos+self.charpos
+ self.fp.seek(pos)
+ ##print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n))
+ self.fp.seek(pos0)
+ return
+
+ def seek(self, pos):
+ '''
+ Seeks the parser to the given position.
+ '''
+ self.fp.seek(pos)
+ # reset the status for nextline()
+ self.bufpos = pos
+ self.buf = ''
+ self.charpos = 0
+ # reset the status for nexttoken()
+ self.parse1 = self.parse_main
+ self.tokens = []
+ return
+
+ def fillbuf(self):
+ if self.charpos < len(self.buf): return
+ # fetch next chunk.
+ self.bufpos = self.fp.tell()
+ self.buf = self.fp.read(self.BUFSIZ)
+ if not self.buf:
+ raise PSEOF('Unexpected EOF')
+ self.charpos = 0
+ return
+
+ def parse_main(self, s, i):
+ m = NONSPC.search(s, i)
+ if not m:
+ return (self.parse_main, len(s))
+ j = m.start(0)
+ c = s[j]
+ self.tokenstart = self.bufpos+j
+ if c == '%':
+ self.token = '%'
+ return (self.parse_comment, j+1)
+ if c == '/':
+ self.token = ''
+ return (self.parse_literal, j+1)
+ if c in '-+' or c.isdigit():
+ self.token = c
+ return (self.parse_number, j+1)
+ if c == '.':
+ self.token = c
+ return (self.parse_float, j+1)
+ if c.isalpha():
+ self.token = c
+ return (self.parse_keyword, j+1)
+ if c == '(':
+ self.token = ''
+ self.paren = 1
+ return (self.parse_string, j+1)
+ if c == '<':
+ self.token = ''
+ return (self.parse_wopen, j+1)
+ if c == '>':
+ self.token = ''
+ return (self.parse_wclose, j+1)
+ self.add_token(KWD(c))
+ return (self.parse_main, j+1)
+
+ def add_token(self, obj):
+ self.tokens.append((self.tokenstart, obj))
+ return
+
+ def parse_comment(self, s, i):
+ m = EOL.search(s, i)
+ if not m:
+ self.token += s[i:]
+ return (self.parse_comment, len(s))
+ j = m.start(0)
+ self.token += s[i:j]
+ # We ignore comments.
+ #self.tokens.append(self.token)
+ return (self.parse_main, j)
+
+ def parse_literal(self, s, i):
+ m = END_LITERAL.search(s, i)
+ if not m:
+ self.token += s[i:]
+ return (self.parse_literal, len(s))
+ j = m.start(0)
+ self.token += s[i:j]
+ c = s[j]
+ if c == '#':
+ self.hex = ''
+ return (self.parse_literal_hex, j+1)
+ self.add_token(LIT(self.token))
+ return (self.parse_main, j)
+
+ def parse_literal_hex(self, s, i):
+ c = s[i]
+ if HEX.match(c) and len(self.hex) < 2:
+ self.hex += c
+ return (self.parse_literal_hex, i+1)
+ if self.hex:
+ self.token += chr(int(self.hex, 16))
+ return (self.parse_literal, i)
+
+ def parse_number(self, s, i):
+ m = END_NUMBER.search(s, i)
+ if not m:
+ self.token += s[i:]
+ return (self.parse_number, len(s))
+ j = m.start(0)
+ self.token += s[i:j]
+ c = s[j]
+ if c == '.':
+ self.token += c
+ return (self.parse_float, j+1)
+ try:
+ self.add_token(int(self.token))
+ except ValueError:
+ pass
+ return (self.parse_main, j)
+ def parse_float(self, s, i):
+ m = END_NUMBER.search(s, i)
+ if not m:
+ self.token += s[i:]
+ return (self.parse_float, len(s))
+ j = m.start(0)
+ self.token += s[i:j]
+ self.add_token(float(self.token))
+ return (self.parse_main, j)
+
+ def parse_keyword(self, s, i):
+ m = END_KEYWORD.search(s, i)
+ if not m:
+ self.token += s[i:]
+ return (self.parse_keyword, len(s))
+ j = m.start(0)
+ self.token += s[i:j]
+ if self.token == 'true':
+ token = True
+ elif self.token == 'false':
+ token = False
+ else:
+ token = KWD(self.token)
+ self.add_token(token)
+ return (self.parse_main, j)
+
+ def parse_string(self, s, i):
+ m = END_STRING.search(s, i)
+ if not m:
+ self.token += s[i:]
+ return (self.parse_string, len(s))
+ j = m.start(0)
+ self.token += s[i:j]
+ c = s[j]
+ if c == '\\':
+ self.oct = ''
+ return (self.parse_string_1, j+1)
+ if c == '(':
+ self.paren += 1
+ self.token += c
+ return (self.parse_string, j+1)
+ if c == ')':
+ self.paren -= 1
+ if self.paren:
+ self.token += c
+ return (self.parse_string, j+1)
+ self.add_token(self.token)
+ return (self.parse_main, j+1)
+ def parse_string_1(self, s, i):
+ c = s[i]
+ if OCT_STRING.match(c) and len(self.oct) < 3:
+ self.oct += c
+ return (self.parse_string_1, i+1)
+ if self.oct:
+ self.token += chr(int(self.oct, 8))
+ return (self.parse_string, i)
+ if c in ESC_STRING:
+ self.token += chr(ESC_STRING[c])
+ return (self.parse_string, i+1)
+
+ def parse_wopen(self, s, i):
+ c = s[i]
+ if c.isspace() or HEX.match(c):
+ return (self.parse_hexstring, i)
+ if c == '<':
+ self.add_token(KEYWORD_DICT_BEGIN)
+ i += 1
+ return (self.parse_main, i)
+
+ def parse_wclose(self, s, i):
+ c = s[i]
+ if c == '>':
+ self.add_token(KEYWORD_DICT_END)
+ i += 1
+ return (self.parse_main, i)
+
+ def parse_hexstring(self, s, i):
+ m = END_HEX_STRING.search(s, i)
+ if not m:
+ self.token += s[i:]
+ return (self.parse_hexstring, len(s))
+ j = m.start(0)
+ self.token += s[i:j]
+ token = HEX_PAIR.sub(lambda m: chr(int(m.group(0), 16)),
+ SPC.sub('', self.token))
+ self.add_token(token)
+ return (self.parse_main, j)
+
+ def nexttoken(self):
+ while not self.tokens:
+ self.fillbuf()
+ (self.parse1, self.charpos) = self.parse1(self.buf, self.charpos)
+ token = self.tokens.pop(0)
+ return token
+
+ def nextline(self):
+ '''
+ Fetches a next line that ends either with \\r or \\n.
+ '''
+ linebuf = ''
+ linepos = self.bufpos + self.charpos
+ eol = False
+ while 1:
+ self.fillbuf()
+ if eol:
+ c = self.buf[self.charpos]
+ # handle '\r\n'
+ if c == '\n':
+ linebuf += c
+ self.charpos += 1
+ break
+ m = EOL.search(self.buf, self.charpos)
+ if m:
+ linebuf += self.buf[self.charpos:m.end(0)]
+ self.charpos = m.end(0)
+ if linebuf[-1] == '\r':
+ eol = True
+ else:
+ break
+ else:
+ linebuf += self.buf[self.charpos:]
+ self.charpos = len(self.buf)
+ return (linepos, linebuf)
+
+ def revreadlines(self):
+ '''
+ Fetches a next line backword. This is used to locate
+ the trailers at the end of a file.
+ '''
+ self.fp.seek(0, 2)
+ pos = self.fp.tell()
+ buf = ''
+ while 0 < pos:
+ prevpos = pos
+ pos = max(0, pos-self.BUFSIZ)
+ self.fp.seek(pos)
+ s = self.fp.read(prevpos-pos)
+ if not s: break
+ while 1:
+ n = max(s.rfind('\r'), s.rfind('\n'))
+ if n == -1:
+ buf = s + buf
+ break
+ yield s[n:]+buf
+ s = s[:n]
+ buf = ''
+ return
+
+
+## PSStackParser
+##
+class PSStackParser(PSBaseParser):
+
+ def __init__(self, fp):
+ PSBaseParser.__init__(self, fp)
+ self.reset()
+ return
+
+ def reset(self):
+ self.context = []
+ self.curtype = None
+ self.curstack = []
+ self.results = []
+ return
+
+ def seek(self, pos):
+ PSBaseParser.seek(self, pos)
+ self.reset()
+ return
+
+ def push(self, *objs):
+ self.curstack.extend(objs)
+ return
+ def pop(self, n):
+ objs = self.curstack[-n:]
+ self.curstack[-n:] = []
+ return objs
+ def popall(self):
+ objs = self.curstack
+ self.curstack = []
+ return objs
+ def add_results(self, *objs):
+ self.results.extend(objs)
+ return
+
+ def start_type(self, pos, type):
+ self.context.append((pos, self.curtype, self.curstack))
+ (self.curtype, self.curstack) = (type, [])
+ return
+ def end_type(self, type):
+ if self.curtype != type:
+ raise PSTypeError('Type mismatch: %r != %r' % (self.curtype, type))
+ objs = [ obj for (_,obj) in self.curstack ]
+ (pos, self.curtype, self.curstack) = self.context.pop()
+ return (pos, objs)
+
+ def do_keyword(self, pos, token):
+ return
+
+ def nextobject(self, direct=False):
+ '''
+ Yields a list of objects: keywords, literals, strings,
+ numbers, arrays and dictionaries. Arrays and dictionaries
+ are represented as Python sequence and dictionaries.
+ '''
+ while not self.results:
+ (pos, token) = self.nexttoken()
+ ##print (pos,token), (self.curtype, self.curstack)
+ if (isinstance(token, int) or
+ isinstance(token, float) or
+ isinstance(token, bool) or
+ isinstance(token, str) or
+ isinstance(token, PSLiteral)):
+ # normal token
+ self.push((pos, token))
+ elif token == KEYWORD_ARRAY_BEGIN:
+ # begin array
+ self.start_type(pos, 'a')
+ elif token == KEYWORD_ARRAY_END:
+ # end array
+ try:
+ self.push(self.end_type('a'))
+ except PSTypeError:
+ if STRICT: raise
+ elif token == KEYWORD_DICT_BEGIN:
+ # begin dictionary
+ self.start_type(pos, 'd')
+ elif token == KEYWORD_DICT_END:
+ # end dictionary
+ try:
+ (pos, objs) = self.end_type('d')
+ if len(objs) % 2 != 0:
+ raise PSSyntaxError(
+ 'Invalid dictionary construct: %r' % objs)
+ d = dict((literal_name(k), v) \
+ for (k,v) in choplist(2, objs))
+ self.push((pos, d))
+ except PSTypeError:
+ if STRICT: raise
+ else:
+ self.do_keyword(pos, token)
+ if self.context:
+ continue
+ else:
+ if direct:
+ return self.pop(1)[0]
+ self.flush()
+ obj = self.results.pop(0)
+ return obj
+
+
+LITERAL_CRYPT = PSLiteralTable.intern('Crypt')
+LITERALS_FLATE_DECODE = (PSLiteralTable.intern('FlateDecode'), PSLiteralTable.intern('Fl'))
+LITERALS_LZW_DECODE = (PSLiteralTable.intern('LZWDecode'), PSLiteralTable.intern('LZW'))
+LITERALS_ASCII85_DECODE = (PSLiteralTable.intern('ASCII85Decode'), PSLiteralTable.intern('A85'))
+
+
+## PDF Objects
+##
+class PDFObject(PSObject): pass
+
+class PDFException(PSException): pass
+class PDFTypeError(PDFException): pass
+class PDFValueError(PDFException): pass
+class PDFNotImplementedError(PSException): pass
+
+
+## PDFObjRef
+##
+class PDFObjRef(PDFObject):
+
+ def __init__(self, doc, objid, genno):
+ if objid == 0:
+ if STRICT:
+ raise PDFValueError('PDF object id cannot be 0.')
+ self.doc = doc
+ self.objid = objid
+ self.genno = genno
+ return
+
+ def __repr__(self):
+ return '' % (self.objid, self.genno)
+
+ def resolve(self):
+ return self.doc.getobj(self.objid)
+
+
+# resolve
+def resolve1(x):
+ '''
+ Resolve an object. If this is an array or dictionary,
+ it may still contains some indirect objects inside.
+ '''
+ while isinstance(x, PDFObjRef):
+ x = x.resolve()
+ return x
+
+def resolve_all(x):
+ '''
+ Recursively resolve X and all the internals.
+ Make sure there is no indirect reference within the nested object.
+ This procedure might be slow.
+ '''
+ while isinstance(x, PDFObjRef):
+ x = x.resolve()
+ if isinstance(x, list):
+ x = [ resolve_all(v) for v in x ]
+ elif isinstance(x, dict):
+ for (k,v) in x.iteritems():
+ x[k] = resolve_all(v)
+ return x
+
+def decipher_all(decipher, objid, genno, x):
+ '''
+ Recursively decipher X.
+ '''
+ if isinstance(x, str):
+ return decipher(objid, genno, x)
+ decf = lambda v: decipher_all(decipher, objid, genno, v)
+ if isinstance(x, list):
+ x = [decf(v) for v in x]
+ elif isinstance(x, dict):
+ x = dict((k, decf(v)) for (k, v) in x.iteritems())
+ return x
+
+
+# Type cheking
+def int_value(x):
+ x = resolve1(x)
+ if not isinstance(x, int):
+ if STRICT:
+ raise PDFTypeError('Integer required: %r' % x)
+ return 0
+ return x
+
+def float_value(x):
+ x = resolve1(x)
+ if not isinstance(x, float):
+ if STRICT:
+ raise PDFTypeError('Float required: %r' % x)
+ return 0.0
+ return x
+
+def num_value(x):
+ x = resolve1(x)
+ if not (isinstance(x, int) or isinstance(x, float)):
+ if STRICT:
+ raise PDFTypeError('Int or Float required: %r' % x)
+ return 0
+ return x
+
+def str_value(x):
+ x = resolve1(x)
+ if not isinstance(x, str):
+ if STRICT:
+ raise PDFTypeError('String required: %r' % x)
+ return ''
+ return x
+
+def list_value(x):
+ x = resolve1(x)
+ if not (isinstance(x, list) or isinstance(x, tuple)):
+ if STRICT:
+ raise PDFTypeError('List required: %r' % x)
+ return []
+ return x
+
+def dict_value(x):
+ x = resolve1(x)
+ if not isinstance(x, dict):
+ if STRICT:
+ raise PDFTypeError('Dict required: %r' % x)
+ return {}
+ return x
+
+def stream_value(x):
+ x = resolve1(x)
+ if not isinstance(x, PDFStream):
+ if STRICT:
+ raise PDFTypeError('PDFStream required: %r' % x)
+ return PDFStream({}, '')
+ return x
+
+# ascii85decode(data)
+def ascii85decode(data):
+ n = b = 0
+ out = ''
+ for c in data:
+ if '!' <= c and c <= 'u':
+ n += 1
+ b = b*85+(ord(c)-33)
+ if n == 5:
+ out += struct.pack('>L',b)
+ n = b = 0
+ elif c == 'z':
+ assert n == 0
+ out += '\0\0\0\0'
+ elif c == '~':
+ if n:
+ for _ in range(5-n):
+ b = b*85+84
+ out += struct.pack('>L',b)[:n-1]
+ break
+ return out
+
+
+## PDFStream type
+class PDFStream(PDFObject):
+ def __init__(self, dic, rawdata, decipher=None):
+ length = int_value(dic.get('Length', 0))
+ eol = rawdata[length:]
+ # quick and dirty fix for false length attribute,
+ # might not work if the pdf stream parser has a problem
+ if decipher != None and decipher.__name__ == 'decrypt_aes':
+ if (len(rawdata) % 16) != 0:
+ cutdiv = len(rawdata) // 16
+ rawdata = rawdata[:16*cutdiv]
+ else:
+ if eol in ('\r', '\n', '\r\n'):
+ rawdata = rawdata[:length]
+
+ self.dic = dic
+ self.rawdata = rawdata
+ self.decipher = decipher
+ self.data = None
+ self.decdata = None
+ self.objid = None
+ self.genno = None
+ return
+
+ def set_objid(self, objid, genno):
+ self.objid = objid
+ self.genno = genno
+ return
+
+ def __repr__(self):
+ if self.rawdata:
+ return '' % \
+ (self.objid, len(self.rawdata), self.dic)
+ else:
+ return '' % \
+ (self.objid, len(self.data), self.dic)
+
+ def decode(self):
+ assert self.data is None and self.rawdata is not None
+ data = self.rawdata
+ if self.decipher:
+ # Handle encryption
+ data = self.decipher(self.objid, self.genno, data)
+ if gen_xref_stm:
+ self.decdata = data # keep decrypted data
+ if 'Filter' not in self.dic:
+ self.data = data
+ self.rawdata = None
+ ##print self.dict
+ return
+ filters = self.dic['Filter']
+ if not isinstance(filters, list):
+ filters = [ filters ]
+ for f in filters:
+ if f in LITERALS_FLATE_DECODE:
+ # will get errors if the document is encrypted.
+ data = zlib.decompress(data)
+ elif f in LITERALS_LZW_DECODE:
+ data = ''.join(LZWDecoder(StringIO(data)).run())
+ elif f in LITERALS_ASCII85_DECODE:
+ data = ascii85decode(data)
+ elif f == LITERAL_CRYPT:
+ raise PDFNotImplementedError('/Crypt filter is unsupported')
+ else:
+ raise PDFNotImplementedError('Unsupported filter: %r' % f)
+ # apply predictors
+ if 'DP' in self.dic:
+ params = self.dic['DP']
+ else:
+ params = self.dic.get('DecodeParms', {})
+ if 'Predictor' in params:
+ pred = int_value(params['Predictor'])
+ if pred:
+ if pred != 12:
+ raise PDFNotImplementedError(
+ 'Unsupported predictor: %r' % pred)
+ if 'Columns' not in params:
+ raise PDFValueError(
+ 'Columns undefined for predictor=12')
+ columns = int_value(params['Columns'])
+ buf = ''
+ ent0 = '\x00' * columns
+ for i in xrange(0, len(data), columns+1):
+ pred = data[i]
+ ent1 = data[i+1:i+1+columns]
+ if pred == '\x02':
+ ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \
+ for (a,b) in zip(ent0,ent1))
+ buf += ent1
+ ent0 = ent1
+ data = buf
+ self.data = data
+ self.rawdata = None
+ return
+
+ def get_data(self):
+ if self.data is None:
+ self.decode()
+ return self.data
+
+ def get_rawdata(self):
+ return self.rawdata
+
+ def get_decdata(self):
+ if self.decdata is not None:
+ return self.decdata
+ data = self.rawdata
+ if self.decipher and data:
+ # Handle encryption
+ data = self.decipher(self.objid, self.genno, data)
+ return data
+
+
+## PDF Exceptions
+##
+class PDFSyntaxError(PDFException): pass
+class PDFNoValidXRef(PDFSyntaxError): pass
+class PDFEncryptionError(PDFException): pass
+class PDFPasswordIncorrect(PDFEncryptionError): pass
+
+# some predefined literals and keywords.
+LITERAL_OBJSTM = PSLiteralTable.intern('ObjStm')
+LITERAL_XREF = PSLiteralTable.intern('XRef')
+LITERAL_PAGE = PSLiteralTable.intern('Page')
+LITERAL_PAGES = PSLiteralTable.intern('Pages')
+LITERAL_CATALOG = PSLiteralTable.intern('Catalog')
+
+
+## XRefs
+##
+
+## PDFXRef
+##
+class PDFXRef(object):
+
+ def __init__(self):
+ self.offsets = None
+ return
+
+ def __repr__(self):
+ return '' % len(self.offsets)
+
+ def objids(self):
+ return self.offsets.iterkeys()
+
+ def load(self, parser):
+ self.offsets = {}
+ while 1:
+ try:
+ (pos, line) = parser.nextline()
+ except PSEOF:
+ raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
+ if not line:
+ raise PDFNoValidXRef('Premature eof: %r' % parser)
+ if line.startswith('trailer'):
+ parser.seek(pos)
+ break
+ f = line.strip().split(' ')
+ if len(f) != 2:
+ raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line))
+ try:
+ (start, nobjs) = map(int, f)
+ except ValueError:
+ raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
+ for objid in xrange(start, start+nobjs):
+ try:
+ (_, line) = parser.nextline()
+ except PSEOF:
+ raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
+ f = line.strip().split(' ')
+ if len(f) != 3:
+ raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line))
+ (pos, genno, use) = f
+ if use != 'n': continue
+ self.offsets[objid] = (int(genno), int(pos))
+ self.load_trailer(parser)
+ return
+
+ KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
+ def load_trailer(self, parser):
+ try:
+ (_,kwd) = parser.nexttoken()
+ assert kwd is self.KEYWORD_TRAILER
+ (_,dic) = parser.nextobject(direct=True)
+ except PSEOF:
+ x = parser.pop(1)
+ if not x:
+ raise PDFNoValidXRef('Unexpected EOF - file corrupted')
+ (_,dic) = x[0]
+ self.trailer = dict_value(dic)
+ return
+
+ def getpos(self, objid):
+ try:
+ (genno, pos) = self.offsets[objid]
+ except KeyError:
+ raise
+ return (None, pos)
+
+
+## PDFXRefStream
+##
+class PDFXRefStream(object):
+
+ def __init__(self):
+ self.index = None
+ self.data = None
+ self.entlen = None
+ self.fl1 = self.fl2 = self.fl3 = None
+ return
+
+ def __repr__(self):
+ return '' % self.index
+
+ def objids(self):
+ for first, size in self.index:
+ for objid in xrange(first, first + size):
+ yield objid
+
+ def load(self, parser, debug=0):
+ (_,objid) = parser.nexttoken() # ignored
+ (_,genno) = parser.nexttoken() # ignored
+ (_,kwd) = parser.nexttoken()
+ (_,stream) = parser.nextobject()
+ if not isinstance(stream, PDFStream) or \
+ stream.dic['Type'] is not LITERAL_XREF:
+ raise PDFNoValidXRef('Invalid PDF stream spec.')
+ size = stream.dic['Size']
+ index = stream.dic.get('Index', (0,size))
+ self.index = zip(islice(index, 0, None, 2),
+ islice(index, 1, None, 2))
+ (self.fl1, self.fl2, self.fl3) = stream.dic['W']
+ self.data = stream.get_data()
+ self.entlen = self.fl1+self.fl2+self.fl3
+ self.trailer = stream.dic
+ return
+
+ def getpos(self, objid):
+ offset = 0
+ for first, size in self.index:
+ if first <= objid and objid < (first + size):
+ break
+ offset += size
+ else:
+ raise KeyError(objid)
+ i = self.entlen * ((objid - first) + offset)
+ ent = self.data[i:i+self.entlen]
+ f1 = nunpack(ent[:self.fl1], 1)
+ if f1 == 1:
+ pos = nunpack(ent[self.fl1:self.fl1+self.fl2])
+ genno = nunpack(ent[self.fl1+self.fl2:])
+ return (None, pos)
+ elif f1 == 2:
+ objid = nunpack(ent[self.fl1:self.fl1+self.fl2])
+ index = nunpack(ent[self.fl1+self.fl2:])
+ return (objid, index)
+ # this is a free object
+ raise KeyError(objid)
+
+
+## PDFDocument
+##
+## A PDFDocument object represents a PDF document.
+## Since a PDF file is usually pretty big, normally it is not loaded
+## at once. Rather it is parsed dynamically as processing goes.
+## A PDF parser is associated with the document.
+##
+class PDFDocument(object):
+
+ def __init__(self):
+ self.xrefs = []
+ self.objs = {}
+ self.parsed_objs = {}
+ self.root = None
+ self.catalog = None
+ self.parser = None
+ self.encryption = None
+ self.decipher = None
+ return
+
+ # set_parser(parser)
+ # Associates the document with an (already initialized) parser object.
+ def set_parser(self, parser):
+ if self.parser: return
+ self.parser = parser
+ # The document is set to be temporarily ready during collecting
+ # all the basic information about the document, e.g.
+ # the header, the encryption information, and the access rights
+ # for the document.
+ self.ready = True
+ # Retrieve the information of each header that was appended
+ # (maybe multiple times) at the end of the document.
+ self.xrefs = parser.read_xref()
+ for xref in self.xrefs:
+ trailer = xref.trailer
+ if not trailer: continue
+
+ # If there's an encryption info, remember it.
+ if 'Encrypt' in trailer:
+ #assert not self.encryption
+ try:
+ self.encryption = (list_value(trailer['ID']),
+ dict_value(trailer['Encrypt']))
+ # fix for bad files
+ except:
+ self.encryption = ('ffffffffffffffffffffffffffffffffffff',
+ dict_value(trailer['Encrypt']))
+ if 'Root' in trailer:
+ self.set_root(dict_value(trailer['Root']))
+ break
+ else:
+ raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
+ # The document is set to be non-ready again, until all the
+ # proper initialization (asking the password key and
+ # verifying the access permission, so on) is finished.
+ self.ready = False
+ return
+
+ # set_root(root)
+ # Set the Root dictionary of the document.
+ # Each PDF file must have exactly one /Root dictionary.
+ def set_root(self, root):
+ self.root = root
+ self.catalog = dict_value(self.root)
+ if self.catalog.get('Type') is not LITERAL_CATALOG:
+ if STRICT:
+ raise PDFSyntaxError('Catalog not found!')
+ return
+ # initialize(password='')
+ # Perform the initialization with a given password.
+ # This step is mandatory even if there's no password associated
+ # with the document.
+ def initialize(self, password=''):
+ if not self.encryption:
+ self.is_printable = self.is_modifiable = self.is_extractable = True
+ self.ready = True
+ return
+ (docid, param) = self.encryption
+ type = literal_name(param['Filter'])
+ if type == 'Adobe.APS':
+ return self.initialize_adobe_ps(password, docid, param)
+ if type == 'Standard':
+ return self.initialize_standard(password, docid, param)
+ if type == 'EBX_HANDLER':
+ return self.initialize_ebx(password, docid, param)
+ raise PDFEncryptionError('Unknown filter: param=%r' % param)
+
+ def initialize_adobe_ps(self, password, docid, param):
+ global KEYFILEPATH
+ self.decrypt_key = self.genkey_adobe_ps(param)
+ self.genkey = self.genkey_v4
+ self.decipher = self.decrypt_aes
+ self.ready = True
+ return
+
+ def genkey_adobe_ps(self, param):
+ # nice little offline principal keys dictionary
+ # global static principal key for German Onleihe / Bibliothek Digital
+ principalkeys = { 'bibliothek-digital.de': 'rRwGv2tbpKov1krvv7PO0ws9S436/lArPlfipz5Pqhw='.decode('base64')}
+ self.is_printable = self.is_modifiable = self.is_extractable = True
+ length = int_value(param.get('Length', 0)) / 8
+ edcdata = str_value(param.get('EDCData')).decode('base64')
+ pdrllic = str_value(param.get('PDRLLic')).decode('base64')
+ pdrlpol = str_value(param.get('PDRLPol')).decode('base64')
+ edclist = []
+ for pair in edcdata.split('\n'):
+ edclist.append(pair)
+ # principal key request
+ for key in principalkeys:
+ if key in pdrllic:
+ principalkey = principalkeys[key]
+ else:
+ raise ADEPTError('Cannot find principal key for this pdf')
+ shakey = SHA256(principalkey)
+ ivector = 16 * chr(0)
+ plaintext = AES.new(shakey,AES.MODE_CBC,ivector).decrypt(edclist[9].decode('base64'))
+ if plaintext[-16:] != 16 * chr(16):
+ raise ADEPTError('Offlinekey cannot be decrypted, aborting ...')
+ pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
+ if ord(pdrlpol[-1]) < 1 or ord(pdrlpol[-1]) > 16:
+ raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
+ else:
+ cutter = -1 * ord(pdrlpol[-1])
+ pdrlpol = pdrlpol[:cutter]
+ return plaintext[:16]
+
+ PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
+ '\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
+ # experimental aes pw support
+ def initialize_standard(self, password, docid, param):
+ # copy from a global variable
+ V = int_value(param.get('V', 0))
+ if (V <=0 or V > 4):
+ raise PDFEncryptionError('Unknown algorithm: param=%r' % param)
+ length = int_value(param.get('Length', 40)) # Key length (bits)
+ O = str_value(param['O'])
+ R = int_value(param['R']) # Revision
+ if 5 <= R:
+ raise PDFEncryptionError('Unknown revision: %r' % R)
+ U = str_value(param['U'])
+ P = int_value(param['P'])
+ try:
+ EncMetadata = str_value(param['EncryptMetadata'])
+ except:
+ EncMetadata = 'True'
+ self.is_printable = bool(P & 4)
+ self.is_modifiable = bool(P & 8)
+ self.is_extractable = bool(P & 16)
+ self.is_annotationable = bool(P & 32)
+ self.is_formsenabled = bool(P & 256)
+ self.is_textextractable = bool(P & 512)
+ self.is_assemblable = bool(P & 1024)
+ self.is_formprintable = bool(P & 2048)
+ # Algorithm 3.2
+ password = (password+self.PASSWORD_PADDING)[:32] # 1
+ hash = hashlib.md5(password) # 2
+ hash.update(O) # 3
+ hash.update(struct.pack('= 3:
+ # Algorithm 3.5
+ hash = hashlib.md5(self.PASSWORD_PADDING) # 2
+ hash.update(docid[0]) # 3
+ x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
+ for i in xrange(1,19+1):
+ k = ''.join( chr(ord(c) ^ i) for c in key )
+ x = ARC4.new(k).decrypt(x)
+ u1 = x+x # 32bytes total
+ if R == 2:
+ is_authenticated = (u1 == U)
+ else:
+ is_authenticated = (u1[:16] == U[:16])
+ if not is_authenticated:
+ raise ADEPTError('Password is not correct.')
+ self.decrypt_key = key
+ # genkey method
+ if V == 1 or V == 2:
+ self.genkey = self.genkey_v2
+ elif V == 3:
+ self.genkey = self.genkey_v3
+ elif V == 4:
+ self.genkey = self.genkey_v2
+ #self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
+ # rc4
+ if V != 4:
+ self.decipher = self.decipher_rc4 # XXX may be AES
+ # aes
+ elif V == 4 and Length == 128:
+ elf.decipher = self.decipher_aes
+ elif V == 4 and Length == 256:
+ raise PDFNotImplementedError('AES256 encryption is currently unsupported')
+ self.ready = True
+ return
+
+ def initialize_ebx(self, password, docid, param):
+ self.is_printable = self.is_modifiable = self.is_extractable = True
+ with open(password, 'rb') as f:
+ keyder = f.read()
+ rsa = RSA(keyder)
+ length = int_value(param.get('Length', 0)) / 8
+ rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
+ rights = zlib.decompress(rights, -15)
+ rights = etree.fromstring(rights)
+ expr = './/{http://ns.adobe.com/adept}encryptedKey'
+ bookkey = ''.join(rights.findtext(expr)).decode('base64')
+ bookkey = rsa.decrypt(bookkey)
+ if bookkey[0] != '\x02':
+ raise ADEPTError('error decrypting book session key')
+ index = bookkey.index('\0') + 1
+ bookkey = bookkey[index:]
+ ebx_V = int_value(param.get('V', 4))
+ ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
+ # added because of improper booktype / decryption book session key errors
+ if length > 0:
+ if len(bookkey) == length:
+ if ebx_V == 3:
+ V = 3
+ else:
+ V = 2
+ elif len(bookkey) == length + 1:
+ V = ord(bookkey[0])
+ bookkey = bookkey[1:]
+ else:
+ print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
+ print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
+ print "bookkey[0] is %d" % ord(bookkey[0])
+ raise ADEPTError('error decrypting book session key - mismatched length')
+ else:
+ # proper length unknown try with whatever you have
+ print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
+ print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
+ print "bookkey[0] is %d" % ord(bookkey[0])
+ if ebx_V == 3:
+ V = 3
+ else:
+ V = 2
+ self.decrypt_key = bookkey
+ self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
+ self.decipher = self.decrypt_rc4
+ self.ready = True
+ return
+
+ # genkey functions
+ def genkey_v2(self, objid, genno):
+ objid = struct.pack(' PDFObjStmRef.maxindex:
+ PDFObjStmRef.maxindex = index
+
+
+## PDFParser
+##
+class PDFParser(PSStackParser):
+
+ def __init__(self, doc, fp):
+ PSStackParser.__init__(self, fp)
+ self.doc = doc
+ self.doc.set_parser(self)
+ return
+
+ def __repr__(self):
+ return ''
+
+ KEYWORD_R = PSKeywordTable.intern('R')
+ KEYWORD_ENDOBJ = PSKeywordTable.intern('endobj')
+ KEYWORD_STREAM = PSKeywordTable.intern('stream')
+ KEYWORD_XREF = PSKeywordTable.intern('xref')
+ KEYWORD_STARTXREF = PSKeywordTable.intern('startxref')
+ def do_keyword(self, pos, token):
+ if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF):
+ self.add_results(*self.pop(1))
+ return
+ if token is self.KEYWORD_ENDOBJ:
+ self.add_results(*self.pop(4))
+ return
+
+ if token is self.KEYWORD_R:
+ # reference to indirect object
+ try:
+ ((_,objid), (_,genno)) = self.pop(2)
+ (objid, genno) = (int(objid), int(genno))
+ obj = PDFObjRef(self.doc, objid, genno)
+ self.push((pos, obj))
+ except PSSyntaxError:
+ pass
+ return
+
+ if token is self.KEYWORD_STREAM:
+ # stream object
+ ((_,dic),) = self.pop(1)
+ dic = dict_value(dic)
+ try:
+ objlen = int_value(dic['Length'])
+ except KeyError:
+ if STRICT:
+ raise PDFSyntaxError('/Length is undefined: %r' % dic)
+ objlen = 0
+ self.seek(pos)
+ try:
+ (_, line) = self.nextline() # 'stream'
+ except PSEOF:
+ if STRICT:
+ raise PDFSyntaxError('Unexpected EOF')
+ return
+ pos += len(line)
+ self.fp.seek(pos)
+ data = self.fp.read(objlen)
+ self.seek(pos+objlen)
+ while 1:
+ try:
+ (linepos, line) = self.nextline()
+ except PSEOF:
+ if STRICT:
+ raise PDFSyntaxError('Unexpected EOF')
+ break
+ if 'endstream' in line:
+ i = line.index('endstream')
+ objlen += i
+ data += line[:i]
+ break
+ objlen += len(line)
+ data += line
+ self.seek(pos+objlen)
+ obj = PDFStream(dic, data, self.doc.decipher)
+ self.push((pos, obj))
+ return
+
+ # others
+ self.push((pos, token))
+ return
+
+ def find_xref(self):
+ # search the last xref table by scanning the file backwards.
+ prev = None
+ for line in self.revreadlines():
+ line = line.strip()
+ if line == 'startxref': break
+ if line:
+ prev = line
+ else:
+ raise PDFNoValidXRef('Unexpected EOF')
+ return int(prev)
+
+ # read xref table
+ def read_xref_from(self, start, xrefs):
+ self.seek(start)
+ self.reset()
+ try:
+ (pos, token) = self.nexttoken()
+ except PSEOF:
+ raise PDFNoValidXRef('Unexpected EOF')
+ if isinstance(token, int):
+ # XRefStream: PDF-1.5
+ if GEN_XREF_STM == 1:
+ global gen_xref_stm
+ gen_xref_stm = True
+ self.seek(pos)
+ self.reset()
+ xref = PDFXRefStream()
+ xref.load(self)
+ else:
+ if token is not self.KEYWORD_XREF:
+ raise PDFNoValidXRef('xref not found: pos=%d, token=%r' %
+ (pos, token))
+ self.nextline()
+ xref = PDFXRef()
+ xref.load(self)
+ xrefs.append(xref)
+ trailer = xref.trailer
+ if 'XRefStm' in trailer:
+ pos = int_value(trailer['XRefStm'])
+ self.read_xref_from(pos, xrefs)
+ if 'Prev' in trailer:
+ # find previous xref
+ pos = int_value(trailer['Prev'])
+ self.read_xref_from(pos, xrefs)
+ return
+
+ # read xref tables and trailers
+ def read_xref(self):
+ xrefs = []
+ trailerpos = None
+ try:
+ pos = self.find_xref()
+ self.read_xref_from(pos, xrefs)
+ except PDFNoValidXRef:
+ # fallback
+ self.seek(0)
+ pat = re.compile(r'^(\d+)\s+(\d+)\s+obj\b')
+ offsets = {}
+ xref = PDFXRef()
+ while 1:
+ try:
+ (pos, line) = self.nextline()
+ except PSEOF:
+ break
+ if line.startswith('trailer'):
+ trailerpos = pos # remember last trailer
+ m = pat.match(line)
+ if not m: continue
+ (objid, genno) = m.groups()
+ offsets[int(objid)] = (0, pos)
+ if not offsets: raise
+ xref.offsets = offsets
+ if trailerpos:
+ self.seek(trailerpos)
+ xref.load_trailer(self)
+ xrefs.append(xref)
+ return xrefs
+
+## PDFObjStrmParser
+##
+class PDFObjStrmParser(PDFParser):
+
+ def __init__(self, data, doc):
+ PSStackParser.__init__(self, StringIO(data))
+ self.doc = doc
+ return
+
+ def flush(self):
+ self.add_results(*self.popall())
+ return
+
+ KEYWORD_R = KWD('R')
+ def do_keyword(self, pos, token):
+ if token is self.KEYWORD_R:
+ # reference to indirect object
+ try:
+ ((_,objid), (_,genno)) = self.pop(2)
+ (objid, genno) = (int(objid), int(genno))
+ obj = PDFObjRef(self.doc, objid, genno)
+ self.push((pos, obj))
+ except PSSyntaxError:
+ pass
+ return
+ # others
+ self.push((pos, token))
+ return
+
+###
+### My own code, for which there is none else to blame
+
+class PDFSerializer(object):
+ def __init__(self, inf, keypath):
+ global GEN_XREF_STM, gen_xref_stm
+ gen_xref_stm = GEN_XREF_STM > 1
+ self.version = inf.read(8)
+ inf.seek(0)
+ self.doc = doc = PDFDocument()
+ parser = PDFParser(doc, inf)
+ doc.initialize(keypath)
+ self.objids = objids = set()
+ for xref in reversed(doc.xrefs):
+ trailer = xref.trailer
+ for objid in xref.objids():
+ objids.add(objid)
+ trailer = dict(trailer)
+ trailer.pop('Prev', None)
+ trailer.pop('XRefStm', None)
+ if 'Encrypt' in trailer:
+ objids.remove(trailer.pop('Encrypt').objid)
+ self.trailer = trailer
+
+ def dump(self, outf):
+ self.outf = outf
+ self.write(self.version)
+ self.write('\n%\xe2\xe3\xcf\xd3\n')
+ doc = self.doc
+ objids = self.objids
+ xrefs = {}
+ maxobj = max(objids)
+ trailer = dict(self.trailer)
+ trailer['Size'] = maxobj + 1
+ for objid in objids:
+ obj = doc.getobj(objid)
+ if isinstance(obj, PDFObjStmRef):
+ xrefs[objid] = obj
+ continue
+ if obj is not None:
+ try:
+ genno = obj.genno
+ except AttributeError:
+ genno = 0
+ xrefs[objid] = (self.tell(), genno)
+ self.serialize_indirect(objid, obj)
+ startxref = self.tell()
+
+ if not gen_xref_stm:
+ self.write('xref\n')
+ self.write('0 %d\n' % (maxobj + 1,))
+ for objid in xrange(0, maxobj + 1):
+ if objid in xrefs:
+ # force the genno to be 0
+ self.write("%010d 00000 n \n" % xrefs[objid][0])
+ else:
+ self.write("%010d %05d f \n" % (0, 65535))
+
+ self.write('trailer\n')
+ self.serialize_object(trailer)
+ self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
+
+ else: # Generate crossref stream.
+
+ # Calculate size of entries
+ maxoffset = max(startxref, maxobj)
+ maxindex = PDFObjStmRef.maxindex
+ fl2 = 2
+ power = 65536
+ while maxoffset >= power:
+ fl2 += 1
+ power *= 256
+ fl3 = 1
+ power = 256
+ while maxindex >= power:
+ fl3 += 1
+ power *= 256
+
+ index = []
+ first = None
+ prev = None
+ data = []
+ # Put the xrefstream's reference in itself
+ startxref = self.tell()
+ maxobj += 1
+ xrefs[maxobj] = (startxref, 0)
+ for objid in sorted(xrefs):
+ if first is None:
+ first = objid
+ elif objid != prev + 1:
+ index.extend((first, prev - first + 1))
+ first = objid
+ prev = objid
+ objref = xrefs[objid]
+ if isinstance(objref, PDFObjStmRef):
+ f1 = 2
+ f2 = objref.stmid
+ f3 = objref.index
+ else:
+ f1 = 1
+ f2 = objref[0]
+ # we force all generation numbers to be 0
+ # f3 = objref[1]
+ f3 = 0
+
+ data.append(struct.pack('>B', f1))
+ data.append(struct.pack('>L', f2)[-fl2:])
+ data.append(struct.pack('>L', f3)[-fl3:])
+ index.extend((first, prev - first + 1))
+ data = zlib.compress(''.join(data))
+ dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
+ 'W': [1, fl2, fl3], 'Length': len(data),
+ 'Filter': LITERALS_FLATE_DECODE[0],
+ 'Root': trailer['Root'],}
+ if 'Info' in trailer:
+ dic['Info'] = trailer['Info']
+ xrefstm = PDFStream(dic, data)
+ self.serialize_indirect(maxobj, xrefstm)
+ self.write('startxref\n%d\n%%%%EOF' % startxref)
+ def write(self, data):
+ self.outf.write(data)
+ self.last = data[-1:]
+
+ def tell(self):
+ return self.outf.tell()
+
+ def escape_string(self, string):
+ string = string.replace('\\', '\\\\')
+ string = string.replace('\n', r'\n')
+ string = string.replace('(', r'\(')
+ string = string.replace(')', r'\)')
+ # get rid of ciando id
+ regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}')
+ if regularexp.match(string): return ('http://www.ciando.com')
+ return string
+
+ def serialize_object(self, obj):
+ if isinstance(obj, dict):
+ # Correct malformed Mac OS resource forks for Stanza
+ if 'ResFork' in obj and 'Type' in obj and 'Subtype' not in obj \
+ and isinstance(obj['Type'], int):
+ obj['Subtype'] = obj['Type']
+ del obj['Type']
+ # end - hope this doesn't have bad effects
+ self.write('<<')
+ for key, val in obj.items():
+ self.write('/%s' % key)
+ self.serialize_object(val)
+ self.write('>>')
+ elif isinstance(obj, list):
+ self.write('[')
+ for val in obj:
+ self.serialize_object(val)
+ self.write(']')
+ elif isinstance(obj, str):
+ self.write('(%s)' % self.escape_string(obj))
+ elif isinstance(obj, bool):
+ if self.last.isalnum():
+ self.write(' ')
+ self.write(str(obj).lower())
+ elif isinstance(obj, (int, long, float)):
+ if self.last.isalnum():
+ self.write(' ')
+ self.write(str(obj))
+ elif isinstance(obj, PDFObjRef):
+ if self.last.isalnum():
+ self.write(' ')
+ self.write('%d %d R' % (obj.objid, 0))
+ elif isinstance(obj, PDFStream):
+ ### If we don't generate cross ref streams the object streams
+ ### are no longer useful, as we have extracted all objects from
+ ### them. Therefore leave them out from the output.
+ if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
+ self.write('(deleted)')
+ else:
+ data = obj.get_decdata()
+ self.serialize_object(obj.dic)
+ self.write('stream\n')
+ self.write(data)
+ self.write('\nendstream')
+ else:
+ data = str(obj)
+ if data[0].isalnum() and self.last.isalnum():
+ self.write(' ')
+ self.write(data)
+
+ def serialize_indirect(self, objid, obj):
+ self.write('%d 0 obj' % (objid,))
+ self.serialize_object(obj)
+ if self.last.isalnum():
+ self.write('\n')
+ self.write('endobj\n')
+
+
+class DecryptionDialog(Tkinter.Frame):
+ def __init__(self, root):
+ Tkinter.Frame.__init__(self, root, border=5)
+ ltext='Select file for decryption\n'
+ self.status = Tkinter.Label(self, text=ltext)
+ self.status.pack(fill=Tkconstants.X, expand=1)
+ body = Tkinter.Frame(self)
+ body.pack(fill=Tkconstants.X, expand=1)
+ sticky = Tkconstants.E + Tkconstants.W
+ body.grid_columnconfigure(1, weight=2)
+ Tkinter.Label(body, text='Key file').grid(row=0)
+ self.keypath = Tkinter.Entry(body, width=30)
+ self.keypath.grid(row=0, column=1, sticky=sticky)
+ if os.path.exists('adeptkey.der'):
+ self.keypath.insert(0, 'adeptkey.der')
+ button = Tkinter.Button(body, text="...", command=self.get_keypath)
+ button.grid(row=0, column=2)
+ Tkinter.Label(body, text='Input file').grid(row=1)
+ self.inpath = Tkinter.Entry(body, width=30)
+ self.inpath.grid(row=1, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text="...", command=self.get_inpath)
+ button.grid(row=1, column=2)
+ Tkinter.Label(body, text='Output file').grid(row=2)
+ self.outpath = Tkinter.Entry(body, width=30)
+ self.outpath.grid(row=2, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text="...", command=self.get_outpath)
+ button.grid(row=2, column=2)
+ buttons = Tkinter.Frame(self)
+ buttons.pack()
+
+
+ botton = Tkinter.Button(
+ buttons, text="Decrypt", width=10, command=self.decrypt)
+ botton.pack(side=Tkconstants.LEFT)
+ Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+ button = Tkinter.Button(
+ buttons, text="Quit", width=10, command=self.quit)
+ button.pack(side=Tkconstants.RIGHT)
+
+
+ def get_keypath(self):
+ keypath = tkFileDialog.askopenfilename(
+ parent=None, title='Select ADEPT key file',
+ defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
+ ('All Files', '.*')])
+ if keypath:
+ keypath = os.path.normpath(os.path.realpath(keypath))
+ self.keypath.delete(0, Tkconstants.END)
+ self.keypath.insert(0, keypath)
+ return
+
+ def get_inpath(self):
+ inpath = tkFileDialog.askopenfilename(
+ parent=None, title='Select ADEPT encrypted PDF file to decrypt',
+ defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
+ ('All files', '.*')])
+ if inpath:
+ inpath = os.path.normpath(os.path.realpath(inpath))
+ self.inpath.delete(0, Tkconstants.END)
+ self.inpath.insert(0, inpath)
+ return
+
+ def get_outpath(self):
+ outpath = tkFileDialog.asksaveasfilename(
+ parent=None, title='Select unencrypted PDF file to produce',
+ defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
+ ('All files', '.*')])
+ if outpath:
+ outpath = os.path.normpath(os.path.realpath(outpath))
+ self.outpath.delete(0, Tkconstants.END)
+ self.outpath.insert(0, outpath)
+ return
+
+ def decrypt(self):
+ keypath = self.keypath.get()
+ inpath = self.inpath.get()
+ outpath = self.outpath.get()
+ if not keypath or not os.path.exists(keypath):
+ # keyfile doesn't exist
+ self.status['text'] = 'Specified Adept key file does not exist'
+ return
+ if not inpath or not os.path.exists(inpath):
+ self.status['text'] = 'Specified input file does not exist'
+ return
+ if not outpath:
+ self.status['text'] = 'Output file not specified'
+ return
+ if inpath == outpath:
+ self.status['text'] = 'Must have different input and output files'
+ return
+ # patch for non-ascii characters
+ argv = [sys.argv[0], keypath, inpath, outpath]
+ self.status['text'] = 'Processing ...'
+ try:
+ cli_main(argv)
+ except Exception, a:
+ self.status['text'] = 'Error: ' + str(a)
+ return
+ self.status['text'] = 'File successfully decrypted.\n'+\
+ 'Close this window or decrypt another pdf file.'
+ return
+
+
+def decryptBook(keypath, inpath, outpath):
+ with open(inpath, 'rb') as inf:
+ try:
+ serializer = PDFSerializer(inf, keypath)
+ except:
+ print "Error serializing pdf. Probably wrong key."
+ return 1
+ # hope this will fix the 'bad file descriptor' problem
+ with open(outpath, 'wb') as outf:
+ # help construct to make sure the method runs to the end
+ try:
+ serializer.dump(outf)
+ except:
+ print "error writing pdf."
+ return 1
+ return 0
+
+
+def cli_main(argv=sys.argv):
+ progname = os.path.basename(argv[0])
+ if RSA is None:
+ print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
+ "separately. Read the top-of-script comment for details." % \
+ (progname,)
+ return 1
+ if len(argv) != 4:
+ print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
+ return 1
+ keypath, inpath, outpath = argv[1:]
+ return decryptBook(keypath, inpath, outpath)
+
+
+def gui_main():
+ root = Tkinter.Tk()
+ if RSA is None:
+ root.withdraw()
+ tkMessageBox.showerror(
+ "INEPT PDF",
+ "This script requires OpenSSL or PyCrypto, which must be installed "
+ "separately. Read the top-of-script comment for details.")
+ return 1
+ root.title('INEPT PDF Decrypter')
+ root.resizable(True, False)
+ root.minsize(370, 0)
+ DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
+ root.mainloop()
+ return 0
+
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ sys.exit(cli_main())
+ sys.exit(gui_main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mobidedrm.py
new file mode 100755
index 0000000..717b0d0
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mobidedrm.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+
+# engine to remove drm from Kindle for Mac and Kindle for PC books
+# for personal use for archiving and converting your ebooks
+
+# PLEASE DO NOT PIRATE EBOOKS!
+
+# We want all authors and publishers, and eBook stores to live
+# long and prosperous lives but at the same time we just want to
+# be able to read OUR books on whatever device we want and to keep
+# readable for a long, long time
+
+# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
+# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
+# and many many others
+
+
+__version__ = '4.4'
+
+class Unbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ def write(self, data):
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+import sys
+import os, csv, getopt
+import string
+import re
+import traceback
+import time
+
+buildXML = False
+
+class DrmException(Exception):
+ pass
+
+if 'calibre' in sys.modules:
+ inCalibre = True
+else:
+ inCalibre = False
+
+if inCalibre:
+ from calibre_plugins.k4mobidedrm import mobidedrm
+ from calibre_plugins.k4mobidedrm import topazextract
+ from calibre_plugins.k4mobidedrm import kgenpids
+else:
+ import mobidedrm
+ import topazextract
+ import kgenpids
+
+
+# cleanup bytestring filenames
+# borrowed from calibre from calibre/src/calibre/__init__.py
+# added in removal of non-printing chars
+# and removal of . at start
+# convert underscores to spaces (we're OK with spaces in file names)
+def cleanup_name(name):
+ _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
+ substitute='_'
+ one = ''.join(char for char in name if char in string.printable)
+ one = _filename_sanitize.sub(substitute, one)
+ one = re.sub(r'\s', ' ', one).strip()
+ one = re.sub(r'^\.+$', '_', one)
+ one = one.replace('..', substitute)
+ # Windows doesn't like path components that end with a period
+ if one.endswith('.'):
+ one = one[:-1]+substitute
+ # Mac and Unix don't like file names that begin with a full stop
+ if len(one) > 0 and one[0] == '.':
+ one = substitute+one[1:]
+ one = one.replace('_',' ')
+ return one
+
+def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
+ global buildXML
+
+
+ # handle the obvious cases at the beginning
+ if not os.path.isfile(infile):
+ print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
+ return 1
+
+ starttime = time.time()
+ print "Starting decryptBook routine."
+
+
+ mobi = True
+ magic3 = file(infile,'rb').read(3)
+ if magic3 == 'TPZ':
+ mobi = False
+
+ bookname = os.path.splitext(os.path.basename(infile))[0]
+
+ if mobi:
+ mb = mobidedrm.MobiBook(infile)
+ else:
+ mb = topazextract.TopazBook(infile)
+
+ title = mb.getBookTitle()
+ print "Processing Book: ", title
+ filenametitle = cleanup_name(title)
+ outfilename = cleanup_name(bookname)
+
+ # generate 'sensible' filename, that will sort with the original name,
+ # but is close to the name from the file.
+ outlength = len(outfilename)
+ comparelength = min(8,min(outlength,len(filenametitle)))
+ copylength = min(max(outfilename.find(' '),8),len(outfilename))
+ if outlength==0:
+ outfilename = filenametitle
+ elif comparelength > 0:
+ if outfilename[:comparelength] == filenametitle[:comparelength]:
+ outfilename = filenametitle
+ else:
+ outfilename = outfilename[:copylength] + " " + filenametitle
+
+ # avoid excessively long file names
+ if len(outfilename)>150:
+ outfilename = outfilename[:150]
+
+ # build pid list
+ md1, md2 = mb.getPIDMetaInfo()
+ pids.extend(kgenpids.getPidList(md1, md2, k4, serials, kInfoFiles))
+
+ print "Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids))
+
+
+ try:
+ mb.processBook(pids)
+
+ except mobidedrm.DrmException, e:
+ print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
+ print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime)
+ return 1
+ except topazextract.TpzDRMError, e:
+ print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
+ print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime)
+ return 1
+ except Exception, e:
+ print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
+ print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime)
+ return 1
+
+ print "Successfully decrypted book after {0:.1f} seconds".format(time.time()-starttime)
+
+ if mobi:
+ if mb.getPrintReplica():
+ outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4')
+ elif mb.getMobiVersion() >= 8:
+ outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3')
+ else:
+ outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
+ mb.getMobiFile(outfile)
+ print "Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename + '_nodrm')
+ return 0
+
+ # topaz:
+ print " Creating NoDRM HTMLZ Archive"
+ zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz')
+ mb.getHTMLZip(zipname)
+
+ print " Creating SVG ZIP Archive"
+ zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
+ mb.getSVGZip(zipname)
+
+ if buildXML:
+ print " Creating XML ZIP Archive"
+ zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
+ mb.getXMLZip(zipname)
+
+ # remove internal temporary directory of Topaz pieces
+ mb.cleanup()
+ print "Saved decrypted Topaz book parts after {0:.1f} seconds".format(time.time()-starttime)
+ return 0
+
+
+def usage(progname):
+ print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks"
+ print "Usage:"
+ print " %s [-k ] [-p ] [-s ] " % progname
+
+#
+# Main
+#
+def main(argv=sys.argv):
+ progname = os.path.basename(argv[0])
+
+ k4 = False
+ kInfoFiles = []
+ serials = []
+ pids = []
+
+ print ('K4MobiDeDrm v%(__version__)s '
+ 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
+ except getopt.GetoptError, err:
+ print str(err)
+ usage(progname)
+ sys.exit(2)
+ if len(args)<2:
+ usage(progname)
+ sys.exit(2)
+
+ for o, a in opts:
+ if o == "-k":
+ if a == None :
+ raise DrmException("Invalid parameter for -k")
+ kInfoFiles.append(a)
+ if o == "-p":
+ if a == None :
+ raise DrmException("Invalid parameter for -p")
+ pids = a.split(',')
+ if o == "-s":
+ if a == None :
+ raise DrmException("Invalid parameter for -s")
+ serials = a.split(',')
+
+ # try with built in Kindle Info files
+ k4 = True
+ if sys.platform.startswith('linux'):
+ k4 = False
+ kInfoFiles = None
+ infile = args[0]
+ outdir = args[1]
+ return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
+
+
+if __name__ == '__main__':
+ sys.stdout=Unbuffered(sys.stdout)
+ sys.exit(main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mutils.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mutils.py
new file mode 100644
index 0000000..1fc08cb
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4mutils.py
@@ -0,0 +1,730 @@
+# standlone set of Mac OSX specific routines needed for KindleBooks
+
+from __future__ import with_statement
+
+import sys
+import os
+import os.path
+import re
+import copy
+import subprocess
+from struct import pack, unpack, unpack_from
+
+class DrmException(Exception):
+ pass
+
+
+# interface to needed routines in openssl's libcrypto
+def _load_crypto_libcrypto():
+ from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
+ Structure, c_ulong, create_string_buffer, addressof, string_at, cast
+ from ctypes.util import find_library
+
+ libcrypto = find_library('crypto')
+ if libcrypto is None:
+ raise DrmException('libcrypto not found')
+ libcrypto = CDLL(libcrypto)
+
+ # From OpenSSL's crypto aes header
+ #
+ # AES_ENCRYPT 1
+ # AES_DECRYPT 0
+ # AES_MAXNR 14 (in bytes)
+ # AES_BLOCK_SIZE 16 (in bytes)
+ #
+ # struct aes_key_st {
+ # unsigned long rd_key[4 *(AES_MAXNR + 1)];
+ # int rounds;
+ # };
+ # typedef struct aes_key_st AES_KEY;
+ #
+ # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
+ #
+ # note: the ivec string, and output buffer are both mutable
+ # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
+ # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc);
+
+ AES_MAXNR = 14
+ c_char_pp = POINTER(c_char_p)
+ c_int_p = POINTER(c_int)
+
+ class AES_KEY(Structure):
+ _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
+ AES_KEY_p = POINTER(AES_KEY)
+
+ def F(restype, name, argtypes):
+ func = getattr(libcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
+
+ AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
+
+ # From OpenSSL's Crypto evp/p5_crpt2.c
+ #
+ # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
+ # const unsigned char *salt, int saltlen, int iter,
+ # int keylen, unsigned char *out);
+
+ PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
+ [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
+
+ class LibCrypto(object):
+ def __init__(self):
+ self._blocksize = 0
+ self._keyctx = None
+ self._iv = 0
+
+ def set_decrypt_key(self, userkey, iv):
+ self._blocksize = len(userkey)
+ if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
+ raise DrmException('AES improper key used')
+ return
+ keyctx = self._keyctx = AES_KEY()
+ self._iv = iv
+ self._userkey = userkey
+ rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
+ if rv < 0:
+ raise DrmException('Failed to initialize AES key')
+
+ def decrypt(self, data):
+ out = create_string_buffer(len(data))
+ mutable_iv = create_string_buffer(self._iv, len(self._iv))
+ keyctx = self._keyctx
+ rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
+ if rv == 0:
+ raise DrmException('AES decryption failed')
+ return out.raw
+
+ def keyivgen(self, passwd, salt, iter, keylen):
+ saltlen = len(salt)
+ passlen = len(passwd)
+ out = create_string_buffer(keylen)
+ rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
+ return out.raw
+ return LibCrypto
+
+def _load_crypto():
+ LibCrypto = None
+ try:
+ LibCrypto = _load_crypto_libcrypto()
+ except (ImportError, DrmException):
+ pass
+ return LibCrypto
+
+LibCrypto = _load_crypto()
+
+#
+# Utility Routines
+#
+
+# crypto digestroutines
+import hashlib
+
+def MD5(message):
+ ctx = hashlib.md5()
+ ctx.update(message)
+ return ctx.digest()
+
+def SHA1(message):
+ ctx = hashlib.sha1()
+ ctx.update(message)
+ return ctx.digest()
+
+def SHA256(message):
+ ctx = hashlib.sha256()
+ ctx.update(message)
+ return ctx.digest()
+
+# Various character maps used to decrypt books. Probably supposed to act as obfuscation
+charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
+charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
+
+# For kinf approach of K4Mac 1.6.X or later
+# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
+# For Mac they seem to re-use charMap2 here
+charMap5 = charMap2
+
+# new in K4M 1.9.X
+testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
+
+
+def encode(data, map):
+ result = ""
+ for char in data:
+ value = ord(char)
+ Q = (value ^ 0x80) // len(map)
+ R = value % len(map)
+ result += map[Q]
+ result += map[R]
+ return result
+
+# Hash the bytes in data and then encode the digest with the characters in map
+def encodeHash(data,map):
+ return encode(MD5(data),map)
+
+# Decode the string in data with the characters in map. Returns the decoded bytes
+def decode(data,map):
+ result = ""
+ for i in range (0,len(data)-1,2):
+ high = map.find(data[i])
+ low = map.find(data[i+1])
+ if (high == -1) or (low == -1) :
+ break
+ value = (((high * len(map)) ^ 0x80) & 0xFF) + low
+ result += pack("B",value)
+ return result
+
+# For K4M 1.6.X and later
+# generate table of prime number less than or equal to int n
+def primes(n):
+ if n==2: return [2]
+ elif n<2: return []
+ s=range(3,n+1,2)
+ mroot = n ** 0.5
+ half=(n+1)/2-1
+ i=0
+ m=3
+ while m <= mroot:
+ if s[i]:
+ j=(m*m-3)/2
+ s[j]=0
+ while j= 0:
+ sernum = resline[pp+19:-1]
+ sernum = sernum.strip()
+ bb = resline.find('"BSD Name" = "')
+ if bb >= 0:
+ bsdname = resline[bb+14:-1]
+ bsdname = bsdname.strip()
+ if (bsdname == 'disk0') and (sernum != None):
+ foundIt = True
+ break
+ if not foundIt:
+ sernum = ''
+ return sernum
+
+def GetUserHomeAppSupKindleDirParitionName():
+ home = os.getenv('HOME')
+ dpath = home + '/Library'
+ cmdline = '/sbin/mount'
+ cmdline = cmdline.encode(sys.getfilesystemencoding())
+ p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+ out1, out2 = p.communicate()
+ reslst = out1.split('\n')
+ cnt = len(reslst)
+ disk = ''
+ foundIt = False
+ for j in xrange(cnt):
+ resline = reslst[j]
+ if resline.startswith('/dev'):
+ (devpart, mpath) = resline.split(' on ')
+ dpart = devpart[5:]
+ pp = mpath.find('(')
+ if pp >= 0:
+ mpath = mpath[:pp-1]
+ if dpath.startswith(mpath):
+ disk = dpart
+ return disk
+
+# uses a sub process to get the UUID of the specified disk partition using ioreg
+def GetDiskPartitionUUID(diskpart):
+ uuidnum = os.getenv('MYUUIDNUMBER')
+ if uuidnum != None:
+ return uuidnum
+ cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
+ cmdline = cmdline.encode(sys.getfilesystemencoding())
+ p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+ out1, out2 = p.communicate()
+ reslst = out1.split('\n')
+ cnt = len(reslst)
+ bsdname = None
+ uuidnum = None
+ foundIt = False
+ nest = 0
+ uuidnest = -1
+ partnest = -2
+ for j in xrange(cnt):
+ resline = reslst[j]
+ if resline.find('{') >= 0:
+ nest += 1
+ if resline.find('}') >= 0:
+ nest -= 1
+ pp = resline.find('"UUID" = "')
+ if pp >= 0:
+ uuidnum = resline[pp+10:-1]
+ uuidnum = uuidnum.strip()
+ uuidnest = nest
+ if partnest == uuidnest and uuidnest > 0:
+ foundIt = True
+ break
+ bb = resline.find('"BSD Name" = "')
+ if bb >= 0:
+ bsdname = resline[bb+14:-1]
+ bsdname = bsdname.strip()
+ if (bsdname == diskpart):
+ partnest = nest
+ else :
+ partnest = -2
+ if partnest == uuidnest and partnest > 0:
+ foundIt = True
+ break
+ if nest == 0:
+ partnest = -2
+ uuidnest = -1
+ uuidnum = None
+ bsdname = None
+ if not foundIt:
+ uuidnum = ''
+ return uuidnum
+
+def GetMACAddressMunged():
+ macnum = os.getenv('MYMACNUM')
+ if macnum != None:
+ return macnum
+ cmdline = '/sbin/ifconfig en0'
+ cmdline = cmdline.encode(sys.getfilesystemencoding())
+ p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+ out1, out2 = p.communicate()
+ reslst = out1.split('\n')
+ cnt = len(reslst)
+ macnum = None
+ foundIt = False
+ for j in xrange(cnt):
+ resline = reslst[j]
+ pp = resline.find('ether ')
+ if pp >= 0:
+ macnum = resline[pp+6:-1]
+ macnum = macnum.strip()
+ # print "original mac", macnum
+ # now munge it up the way Kindle app does
+ # by xoring it with 0xa5 and swapping elements 3 and 4
+ maclst = macnum.split(':')
+ n = len(maclst)
+ if n != 6:
+ fountIt = False
+ break
+ for i in range(6):
+ maclst[i] = int('0x' + maclst[i], 0)
+ mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+ mlst[5] = maclst[5] ^ 0xa5
+ mlst[4] = maclst[3] ^ 0xa5
+ mlst[3] = maclst[4] ^ 0xa5
+ mlst[2] = maclst[2] ^ 0xa5
+ mlst[1] = maclst[1] ^ 0xa5
+ mlst[0] = maclst[0] ^ 0xa5
+ macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
+ foundIt = True
+ break
+ if not foundIt:
+ macnum = ''
+ return macnum
+
+
+# uses unix env to get username instead of using sysctlbyname
+def GetUserName():
+ username = os.getenv('USER')
+ return username
+
+def isNewInstall():
+ home = os.getenv('HOME')
+ # soccer game fan anyone
+ dpath = home + '/Library/Application Support/Kindle/storage/.pes2011'
+ # print dpath, os.path.exists(dpath)
+ if os.path.exists(dpath):
+ return True
+ dpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.pes2011'
+ # print dpath, os.path.exists(dpath)
+ if os.path.exists(dpath):
+ return True
+ return False
+
+
+def GetIDString():
+ # K4Mac now has an extensive set of ids strings it uses
+ # in encoding pids and in creating unique passwords
+ # for use in its own version of CryptUnprotectDataV2
+
+ # BUT Amazon has now become nasty enough to detect when its app
+ # is being run under a debugger and actually changes code paths
+ # including which one of these strings is chosen, all to try
+ # to prevent reverse engineering
+
+ # Sad really ... they will only hurt their own sales ...
+ # true book lovers really want to keep their books forever
+ # and move them to their devices and DRM prevents that so they
+ # will just buy from someplace else that they can remove
+ # the DRM from
+
+ # Amazon should know by now that true book lover's are not like
+ # penniless kids that pirate music, we do not pirate books
+
+ if isNewInstall():
+ mungedmac = GetMACAddressMunged()
+ if len(mungedmac) > 7:
+ print('Using Munged MAC Address for ID: '+mungedmac)
+ return mungedmac
+ sernum = GetVolumeSerialNumber()
+ if len(sernum) > 7:
+ print('Using Volume Serial Number for ID: '+sernum)
+ return sernum
+ diskpart = GetUserHomeAppSupKindleDirParitionName()
+ uuidnum = GetDiskPartitionUUID(diskpart)
+ if len(uuidnum) > 7:
+ print('Using Disk Partition UUID for ID: '+uuidnum)
+ return uuidnum
+ mungedmac = GetMACAddressMunged()
+ if len(mungedmac) > 7:
+ print('Using Munged MAC Address for ID: '+mungedmac)
+ return mungedmac
+ print('Using Fixed constant 9999999999 for ID.')
+ return '9999999999'
+
+
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used by Kindle for Mac versions < 1.6.0
+class CryptUnprotectData(object):
+ def __init__(self):
+ sernum = GetVolumeSerialNumber()
+ if sernum == '':
+ sernum = '9999999999'
+ sp = sernum + '!@#' + GetUserName()
+ passwdData = encode(SHA256(sp),charMap1)
+ salt = '16743'
+ self.crp = LibCrypto()
+ iter = 0x3e8
+ keylen = 0x80
+ key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+ self.key = key_iv[0:32]
+ self.iv = key_iv[32:48]
+ self.crp.set_decrypt_key(self.key, self.iv)
+
+ def decrypt(self, encryptedData):
+ cleartext = self.crp.decrypt(encryptedData)
+ cleartext = decode(cleartext,charMap1)
+ return cleartext
+
+
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used for Kindle for Mac Versions >= 1.6.0
+class CryptUnprotectDataV2(object):
+ def __init__(self):
+ sp = GetUserName() + ':&%:' + GetIDString()
+ passwdData = encode(SHA256(sp),charMap5)
+ # salt generation as per the code
+ salt = 0x0512981d * 2 * 1 * 1
+ salt = str(salt) + GetUserName()
+ salt = encode(salt,charMap5)
+ self.crp = LibCrypto()
+ iter = 0x800
+ keylen = 0x400
+ key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+ self.key = key_iv[0:32]
+ self.iv = key_iv[32:48]
+ self.crp.set_decrypt_key(self.key, self.iv)
+
+ def decrypt(self, encryptedData):
+ cleartext = self.crp.decrypt(encryptedData)
+ cleartext = decode(cleartext, charMap5)
+ return cleartext
+
+
+# unprotect the new header blob in .kinf2011
+# used in Kindle for Mac Version >= 1.9.0
+def UnprotectHeaderData(encryptedData):
+ passwdData = 'header_key_data'
+ salt = 'HEADER.2011'
+ iter = 0x80
+ keylen = 0x100
+ crp = LibCrypto()
+ key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
+ key = key_iv[0:32]
+ iv = key_iv[32:48]
+ crp.set_decrypt_key(key,iv)
+ cleartext = crp.decrypt(encryptedData)
+ return cleartext
+
+
+# implements an Pseudo Mac Version of Windows built-in Crypto routine
+# used for Kindle for Mac Versions >= 1.9.0
+class CryptUnprotectDataV3(object):
+ def __init__(self, entropy):
+ sp = GetUserName() + '+@#$%+' + GetIDString()
+ passwdData = encode(SHA256(sp),charMap2)
+ salt = entropy
+ self.crp = LibCrypto()
+ iter = 0x800
+ keylen = 0x400
+ key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+ self.key = key_iv[0:32]
+ self.iv = key_iv[32:48]
+ self.crp.set_decrypt_key(self.key, self.iv)
+
+ def decrypt(self, encryptedData):
+ cleartext = self.crp.decrypt(encryptedData)
+ cleartext = decode(cleartext, charMap2)
+ return cleartext
+
+
+# Locate the .kindle-info files
+def getKindleInfoFiles():
+ # file searches can take a long time on some systems, so just look in known specific places.
+ kInfoFiles=[]
+ found = False
+ home = os.getenv('HOME')
+ # check for .kinf2011 file in new location (App Store Kindle for Mac)
+ testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011'
+ if os.path.isfile(testpath):
+ kInfoFiles.append(testpath)
+ print('Found k4Mac kinf2011 file: ' + testpath)
+ found = True
+ # check for .kinf2011 files
+ testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011'
+ if os.path.isfile(testpath):
+ kInfoFiles.append(testpath)
+ print('Found k4Mac kinf2011 file: ' + testpath)
+ found = True
+ # check for .rainier-2.1.1-kinf files
+ testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf'
+ if os.path.isfile(testpath):
+ kInfoFiles.append(testpath)
+ print('Found k4Mac rainier file: ' + testpath)
+ found = True
+ # check for .rainier-2.1.1-kinf files
+ testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info'
+ if os.path.isfile(testpath):
+ kInfoFiles.append(testpath)
+ print('Found k4Mac kindle-info file: ' + testpath)
+ found = True
+ if not found:
+ print('No k4Mac kindle-info/rainier/kinf2011 files have been found.')
+ return kInfoFiles
+
+# determine type of kindle info provided and return a
+# database of keynames and values
+def getDBfromFile(kInfoFile):
+ names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
+ DB = {}
+ cnt = 0
+ infoReader = open(kInfoFile, 'r')
+ hdr = infoReader.read(1)
+ data = infoReader.read()
+
+ if data.find('[') != -1 :
+
+ # older style kindle-info file
+ cud = CryptUnprotectData()
+ items = data.split('[')
+ for item in items:
+ if item != '':
+ keyhash, rawdata = item.split(':')
+ keyname = "unknown"
+ for name in names:
+ if encodeHash(name,charMap2) == keyhash:
+ keyname = name
+ break
+ if keyname == "unknown":
+ keyname = keyhash
+ encryptedValue = decode(rawdata,charMap2)
+ cleartext = cud.decrypt(encryptedValue)
+ DB[keyname] = cleartext
+ cnt = cnt + 1
+ if cnt == 0:
+ DB = None
+ return DB
+
+ if hdr == '/':
+
+ # else newer style .kinf file used by K4Mac >= 1.6.0
+ # the .kinf file uses "/" to separate it into records
+ # so remove the trailing "/" to make it easy to use split
+ data = data[:-1]
+ items = data.split('/')
+ cud = CryptUnprotectDataV2()
+
+ # loop through the item records until all are processed
+ while len(items) > 0:
+
+ # get the first item record
+ item = items.pop(0)
+
+ # the first 32 chars of the first record of a group
+ # is the MD5 hash of the key name encoded by charMap5
+ keyhash = item[0:32]
+ keyname = "unknown"
+
+ # the raw keyhash string is also used to create entropy for the actual
+ # CryptProtectData Blob that represents that keys contents
+ # "entropy" not used for K4Mac only K4PC
+ # entropy = SHA1(keyhash)
+
+ # the remainder of the first record when decoded with charMap5
+ # has the ':' split char followed by the string representation
+ # of the number of records that follow
+ # and make up the contents
+ srcnt = decode(item[34:],charMap5)
+ rcnt = int(srcnt)
+
+ # read and store in rcnt records of data
+ # that make up the contents value
+ edlst = []
+ for i in xrange(rcnt):
+ item = items.pop(0)
+ edlst.append(item)
+
+ keyname = "unknown"
+ for name in names:
+ if encodeHash(name,charMap5) == keyhash:
+ keyname = name
+ break
+ if keyname == "unknown":
+ keyname = keyhash
+
+ # the charMap5 encoded contents data has had a length
+ # of chars (always odd) cut off of the front and moved
+ # to the end to prevent decoding using charMap5 from
+ # working properly, and thereby preventing the ensuing
+ # CryptUnprotectData call from succeeding.
+
+ # The offset into the charMap5 encoded contents seems to be:
+ # len(contents) - largest prime number less than or equal to int(len(content)/3)
+ # (in other words split "about" 2/3rds of the way through)
+
+ # move first offsets chars to end to align for decode by charMap5
+ encdata = "".join(edlst)
+ contlen = len(encdata)
+
+ # now properly split and recombine
+ # by moving noffset chars from the start of the
+ # string to the end of the string
+ noffset = contlen - primes(int(contlen/3))[-1]
+ pfx = encdata[0:noffset]
+ encdata = encdata[noffset:]
+ encdata = encdata + pfx
+
+ # decode using charMap5 to get the CryptProtect Data
+ encryptedValue = decode(encdata,charMap5)
+ cleartext = cud.decrypt(encryptedValue)
+ DB[keyname] = cleartext
+ cnt = cnt + 1
+
+ if cnt == 0:
+ DB = None
+ return DB
+
+ # the latest .kinf2011 version for K4M 1.9.1
+ # put back the hdr char, it is needed
+ data = hdr + data
+ data = data[:-1]
+ items = data.split('/')
+
+ # the headerblob is the encrypted information needed to build the entropy string
+ headerblob = items.pop(0)
+ encryptedValue = decode(headerblob, charMap1)
+ cleartext = UnprotectHeaderData(encryptedValue)
+
+ # now extract the pieces in the same way
+ # this version is different from K4PC it scales the build number by multipying by 735
+ pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+ for m in re.finditer(pattern, cleartext):
+ entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
+
+ cud = CryptUnprotectDataV3(entropy)
+
+ # loop through the item records until all are processed
+ while len(items) > 0:
+
+ # get the first item record
+ item = items.pop(0)
+
+ # the first 32 chars of the first record of a group
+ # is the MD5 hash of the key name encoded by charMap5
+ keyhash = item[0:32]
+ keyname = "unknown"
+
+ # unlike K4PC the keyhash is not used in generating entropy
+ # entropy = SHA1(keyhash) + added_entropy
+ # entropy = added_entropy
+
+ # the remainder of the first record when decoded with charMap5
+ # has the ':' split char followed by the string representation
+ # of the number of records that follow
+ # and make up the contents
+ srcnt = decode(item[34:],charMap5)
+ rcnt = int(srcnt)
+
+ # read and store in rcnt records of data
+ # that make up the contents value
+ edlst = []
+ for i in xrange(rcnt):
+ item = items.pop(0)
+ edlst.append(item)
+
+ keyname = "unknown"
+ for name in names:
+ if encodeHash(name,testMap8) == keyhash:
+ keyname = name
+ break
+ if keyname == "unknown":
+ keyname = keyhash
+
+ # the testMap8 encoded contents data has had a length
+ # of chars (always odd) cut off of the front and moved
+ # to the end to prevent decoding using testMap8 from
+ # working properly, and thereby preventing the ensuing
+ # CryptUnprotectData call from succeeding.
+
+ # The offset into the testMap8 encoded contents seems to be:
+ # len(contents) - largest prime number less than or equal to int(len(content)/3)
+ # (in other words split "about" 2/3rds of the way through)
+
+ # move first offsets chars to end to align for decode by testMap8
+ encdata = "".join(edlst)
+ contlen = len(encdata)
+
+ # now properly split and recombine
+ # by moving noffset chars from the start of the
+ # string to the end of the string
+ noffset = contlen - primes(int(contlen/3))[-1]
+ pfx = encdata[0:noffset]
+ encdata = encdata[noffset:]
+ encdata = encdata + pfx
+
+ # decode using testMap8 to get the CryptProtect Data
+ encryptedValue = decode(encdata,testMap8)
+ cleartext = cud.decrypt(encryptedValue)
+ # print keyname
+ # print cleartext
+ DB[keyname] = cleartext
+ cnt = cnt + 1
+
+ if cnt == 0:
+ DB = None
+ return DB
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4pcutils.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4pcutils.py
new file mode 100755
index 0000000..9f9ca07
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/k4pcutils.py
@@ -0,0 +1,455 @@
+#!/usr/bin/env python
+# K4PC Windows specific routines
+
+from __future__ import with_statement
+
+import sys, os, re
+from struct import pack, unpack, unpack_from
+
+from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
+ create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
+ string_at, Structure, c_void_p, cast
+
+import _winreg as winreg
+MAX_PATH = 255
+kernel32 = windll.kernel32
+advapi32 = windll.advapi32
+crypt32 = windll.crypt32
+
+import traceback
+
+# crypto digestroutines
+import hashlib
+
+def MD5(message):
+ ctx = hashlib.md5()
+ ctx.update(message)
+ return ctx.digest()
+
+def SHA1(message):
+ ctx = hashlib.sha1()
+ ctx.update(message)
+ return ctx.digest()
+
+def SHA256(message):
+ ctx = hashlib.sha256()
+ ctx.update(message)
+ return ctx.digest()
+
+# For K4PC 1.9.X
+# use routines in alfcrypto:
+# AES_cbc_encrypt
+# AES_set_decrypt_key
+# PKCS5_PBKDF2_HMAC_SHA1
+
+from alfcrypto import AES_CBC, KeyIVGen
+
+def UnprotectHeaderData(encryptedData):
+ passwdData = 'header_key_data'
+ salt = 'HEADER.2011'
+ iter = 0x80
+ keylen = 0x100
+ key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
+ key = key_iv[0:32]
+ iv = key_iv[32:48]
+ aes=AES_CBC()
+ aes.set_decrypt_key(key, iv)
+ cleartext = aes.decrypt(encryptedData)
+ return cleartext
+
+
+# simple primes table (<= n) calculator
+def primes(n):
+ if n==2: return [2]
+ elif n<2: return []
+ s=range(3,n+1,2)
+ mroot = n ** 0.5
+ half=(n+1)/2-1
+ i=0
+ m=3
+ while m <= mroot:
+ if s[i]:
+ j=(m*m-3)/2
+ s[j]=0
+ while j 0:
+
+ # get the first item record
+ item = items.pop(0)
+
+ # the first 32 chars of the first record of a group
+ # is the MD5 hash of the key name encoded by charMap5
+ keyhash = item[0:32]
+
+ # the raw keyhash string is used to create entropy for the actual
+ # CryptProtectData Blob that represents that keys contents
+ entropy = SHA1(keyhash)
+
+ # the remainder of the first record when decoded with charMap5
+ # has the ':' split char followed by the string representation
+ # of the number of records that follow
+ # and make up the contents
+ srcnt = decode(item[34:],charMap5)
+ rcnt = int(srcnt)
+
+ # read and store in rcnt records of data
+ # that make up the contents value
+ edlst = []
+ for i in xrange(rcnt):
+ item = items.pop(0)
+ edlst.append(item)
+
+ keyname = "unknown"
+ for name in names:
+ if encodeHash(name,charMap5) == keyhash:
+ keyname = name
+ break
+ if keyname == "unknown":
+ keyname = keyhash
+ # the charMap5 encoded contents data has had a length
+ # of chars (always odd) cut off of the front and moved
+ # to the end to prevent decoding using charMap5 from
+ # working properly, and thereby preventing the ensuing
+ # CryptUnprotectData call from succeeding.
+
+ # The offset into the charMap5 encoded contents seems to be:
+ # len(contents)-largest prime number <= int(len(content)/3)
+ # (in other words split "about" 2/3rds of the way through)
+
+ # move first offsets chars to end to align for decode by charMap5
+ encdata = "".join(edlst)
+ contlen = len(encdata)
+ noffset = contlen - primes(int(contlen/3))[-1]
+
+ # now properly split and recombine
+ # by moving noffset chars from the start of the
+ # string to the end of the string
+ pfx = encdata[0:noffset]
+ encdata = encdata[noffset:]
+ encdata = encdata + pfx
+
+ # decode using Map5 to get the CryptProtect Data
+ encryptedValue = decode(encdata,charMap5)
+ DB[keyname] = CryptUnprotectData(encryptedValue, entropy, 1)
+ cnt = cnt + 1
+
+ if cnt == 0:
+ DB = None
+ return DB
+
+ # else newest .kinf2011 style .kinf file
+ # the .kinf file uses "/" to separate it into records
+ # so remove the trailing "/" to make it easy to use split
+ # need to put back the first char read because it it part
+ # of the added entropy blob
+ data = hdr + data[:-1]
+ items = data.split('/')
+
+ # starts with and encoded and encrypted header blob
+ headerblob = items.pop(0)
+ encryptedValue = decode(headerblob, testMap1)
+ cleartext = UnprotectHeaderData(encryptedValue)
+ # now extract the pieces that form the added entropy
+ pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
+ for m in re.finditer(pattern, cleartext):
+ added_entropy = m.group(2) + m.group(4)
+
+
+ # loop through the item records until all are processed
+ while len(items) > 0:
+
+ # get the first item record
+ item = items.pop(0)
+
+ # the first 32 chars of the first record of a group
+ # is the MD5 hash of the key name encoded by charMap5
+ keyhash = item[0:32]
+
+ # the sha1 of raw keyhash string is used to create entropy along
+ # with the added entropy provided above from the headerblob
+ entropy = SHA1(keyhash) + added_entropy
+
+ # the remainder of the first record when decoded with charMap5
+ # has the ':' split char followed by the string representation
+ # of the number of records that follow
+ # and make up the contents
+ srcnt = decode(item[34:],charMap5)
+ rcnt = int(srcnt)
+
+ # read and store in rcnt records of data
+ # that make up the contents value
+ edlst = []
+ for i in xrange(rcnt):
+ item = items.pop(0)
+ edlst.append(item)
+
+ # key names now use the new testMap8 encoding
+ keyname = "unknown"
+ for name in names:
+ if encodeHash(name,testMap8) == keyhash:
+ keyname = name
+ break
+
+ # the testMap8 encoded contents data has had a length
+ # of chars (always odd) cut off of the front and moved
+ # to the end to prevent decoding using testMap8 from
+ # working properly, and thereby preventing the ensuing
+ # CryptUnprotectData call from succeeding.
+
+ # The offset into the testMap8 encoded contents seems to be:
+ # len(contents)-largest prime number <= int(len(content)/3)
+ # (in other words split "about" 2/3rds of the way through)
+
+ # move first offsets chars to end to align for decode by testMap8
+ # by moving noffset chars from the start of the
+ # string to the end of the string
+ encdata = "".join(edlst)
+ contlen = len(encdata)
+ noffset = contlen - primes(int(contlen/3))[-1]
+ pfx = encdata[0:noffset]
+ encdata = encdata[noffset:]
+ encdata = encdata + pfx
+
+ # decode using new testMap8 to get the original CryptProtect Data
+ encryptedValue = decode(encdata,testMap8)
+ cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
+ DB[keyname] = cleartext
+ cnt = cnt + 1
+
+ if cnt == 0:
+ DB = None
+ return DB
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kgenpids.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kgenpids.py
new file mode 100644
index 0000000..b0fbaa4
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kgenpids.py
@@ -0,0 +1,274 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+import sys
+import os, csv
+import binascii
+import zlib
+import re
+from struct import pack, unpack, unpack_from
+
+class DrmException(Exception):
+ pass
+
+global charMap1
+global charMap3
+global charMap4
+
+if 'calibre' in sys.modules:
+ inCalibre = True
+else:
+ inCalibre = False
+
+if inCalibre:
+ if sys.platform.startswith('win'):
+ from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
+
+ if sys.platform.startswith('darwin'):
+ from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
+else:
+ if sys.platform.startswith('win'):
+ from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
+
+ if sys.platform.startswith('darwin'):
+ from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
+
+
+charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
+charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+
+# crypto digestroutines
+import hashlib
+
+def MD5(message):
+ ctx = hashlib.md5()
+ ctx.update(message)
+ return ctx.digest()
+
+def SHA1(message):
+ ctx = hashlib.sha1()
+ ctx.update(message)
+ return ctx.digest()
+
+
+# Encode the bytes in data with the characters in map
+def encode(data, map):
+ result = ""
+ for char in data:
+ value = ord(char)
+ Q = (value ^ 0x80) // len(map)
+ R = value % len(map)
+ result += map[Q]
+ result += map[R]
+ return result
+
+# Hash the bytes in data and then encode the digest with the characters in map
+def encodeHash(data,map):
+ return encode(MD5(data),map)
+
+# Decode the string in data with the characters in map. Returns the decoded bytes
+def decode(data,map):
+ result = ""
+ for i in range (0,len(data)-1,2):
+ high = map.find(data[i])
+ low = map.find(data[i+1])
+ if (high == -1) or (low == -1) :
+ break
+ value = (((high * len(map)) ^ 0x80) & 0xFF) + low
+ result += pack("B",value)
+ return result
+
+#
+# PID generation routines
+#
+
+# Returns two bit at offset from a bit field
+def getTwoBitsFromBitField(bitField,offset):
+ byteNumber = offset // 4
+ bitPosition = 6 - 2*(offset % 4)
+ return ord(bitField[byteNumber]) >> bitPosition & 3
+
+# Returns the six bits at offset from a bit field
+def getSixBitsFromBitField(bitField,offset):
+ offset *= 3
+ value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
+ return value
+
+# 8 bits to six bits encoding from hash to generate PID string
+def encodePID(hash):
+ global charMap3
+ PID = ""
+ for position in range (0,8):
+ PID += charMap3[getSixBitsFromBitField(hash,position)]
+ return PID
+
+# Encryption table used to generate the device PID
+def generatePidEncryptionTable() :
+ table = []
+ for counter1 in range (0,0x100):
+ value = counter1
+ for counter2 in range (0,8):
+ if (value & 1 == 0) :
+ value = value >> 1
+ else :
+ value = value >> 1
+ value = value ^ 0xEDB88320
+ table.append(value)
+ return table
+
+# Seed value used to generate the device PID
+def generatePidSeed(table,dsn) :
+ value = 0
+ for counter in range (0,4) :
+ index = (ord(dsn[counter]) ^ value) &0xFF
+ value = (value >> 8) ^ table[index]
+ return value
+
+# Generate the device PID
+def generateDevicePID(table,dsn,nbRoll):
+ global charMap4
+ seed = generatePidSeed(table,dsn)
+ pidAscii = ""
+ pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
+ index = 0
+ for counter in range (0,nbRoll):
+ pid[index] = pid[index] ^ ord(dsn[counter])
+ index = (index+1) %8
+ for counter in range (0,8):
+ index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
+ pidAscii += charMap4[index]
+ return pidAscii
+
+def crc32(s):
+ return (~binascii.crc32(s,-1))&0xFFFFFFFF
+
+# convert from 8 digit PID to 10 digit PID with checksum
+def checksumPid(s):
+ global charMap4
+ crc = crc32(s)
+ crc = crc ^ (crc >> 16)
+ res = s
+ l = len(charMap4)
+ for i in (0,1):
+ b = crc & 0xff
+ pos = (b // l) ^ (b % l)
+ res += charMap4[pos%l]
+ crc >>= 8
+ return res
+
+
+# old kindle serial number to fixed pid
+def pidFromSerial(s, l):
+ global charMap4
+ crc = crc32(s)
+ arr1 = [0]*l
+ for i in xrange(len(s)):
+ arr1[i%l] ^= ord(s[i])
+ crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
+ for i in xrange(l):
+ arr1[i] ^= crc_bytes[i&3]
+ pid = ""
+ for i in xrange(l):
+ b = arr1[i] & 0xff
+ pid+=charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
+ return pid
+
+
+# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
+def getKindlePid(pidlst, rec209, token, serialnum):
+ # Compute book PID
+ pidHash = SHA1(serialnum+rec209+token)
+ bookPID = encodePID(pidHash)
+ bookPID = checksumPid(bookPID)
+ pidlst.append(bookPID)
+
+ # compute fixed pid for old pre 2.5 firmware update pid as well
+ bookPID = pidFromSerial(serialnum, 7) + "*"
+ bookPID = checksumPid(bookPID)
+ pidlst.append(bookPID)
+
+ return pidlst
+
+
+# parse the Kindleinfo file to calculate the book pid.
+
+keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
+
+def getK4Pids(pidlst, rec209, token, kInfoFile):
+ global charMap1
+ kindleDatabase = None
+ try:
+ kindleDatabase = getDBfromFile(kInfoFile)
+ except Exception, message:
+ print(message)
+ kindleDatabase = None
+ pass
+
+ if kindleDatabase == None :
+ return pidlst
+
+ try:
+ # Get the Mazama Random number
+ MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
+
+ # Get the kindle account token
+ kindleAccountToken = kindleDatabase["kindle.account.tokens"]
+ except KeyError:
+ print "Keys not found in " + kInfoFile
+ return pidlst
+
+ # Get the ID string used
+ encodedIDString = encodeHash(GetIDString(),charMap1)
+
+ # Get the current user name
+ encodedUsername = encodeHash(GetUserName(),charMap1)
+
+ # concat, hash and encode to calculate the DSN
+ DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
+
+ # Compute the device PID (for which I can tell, is used for nothing).
+ table = generatePidEncryptionTable()
+ devicePID = generateDevicePID(table,DSN,4)
+ devicePID = checksumPid(devicePID)
+ pidlst.append(devicePID)
+
+ # Compute book PIDs
+
+ # book pid
+ pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
+ bookPID = encodePID(pidHash)
+ bookPID = checksumPid(bookPID)
+ pidlst.append(bookPID)
+
+ # variant 1
+ pidHash = SHA1(kindleAccountToken+rec209+token)
+ bookPID = encodePID(pidHash)
+ bookPID = checksumPid(bookPID)
+ pidlst.append(bookPID)
+
+ # variant 2
+ pidHash = SHA1(DSN+rec209+token)
+ bookPID = encodePID(pidHash)
+ bookPID = checksumPid(bookPID)
+ pidlst.append(bookPID)
+
+ return pidlst
+
+def getPidList(md1, md2, k4 = True, serials=[], kInfoFiles=[]):
+ pidlst = []
+ if kInfoFiles is None:
+ kInfoFiles = []
+ if k4:
+ kInfoFiles.extend(getKindleInfoFiles())
+ for infoFile in kInfoFiles:
+ try:
+ pidlst = getK4Pids(pidlst, md1, md2, infoFile)
+ except Exception, message:
+ print("Error getting PIDs from " + infoFile + ": " + message)
+ for serialnum in serials:
+ try:
+ pidlst = getKindlePid(pidlst, md1, md2, serialnum)
+ except Exception, message:
+ print("Error getting PIDs from " + serialnum + ": " + message)
+ return pidlst
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kindlepid.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kindlepid.py
new file mode 100755
index 0000000..90a59ad
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/kindlepid.py
@@ -0,0 +1,91 @@
+#!/usr/bin/python
+# Mobipocket PID calculator v0.2 for Amazon Kindle.
+# Copyright (c) 2007, 2009 Igor Skochinsky
+# History:
+# 0.1 Initial release
+# 0.2 Added support for generating PID for iPhone (thanks to mbp)
+# 0.3 changed to autoflush stdout, fixed return code usage
+class Unbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ def write(self, data):
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+import sys
+sys.stdout=Unbuffered(sys.stdout)
+
+import binascii
+
+if sys.hexversion >= 0x3000000:
+ print "This script is incompatible with Python 3.x. Please install Python 2.6.x from python.org"
+ sys.exit(2)
+
+letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+
+def crc32(s):
+ return (~binascii.crc32(s,-1))&0xFFFFFFFF
+
+def checksumPid(s):
+ crc = crc32(s)
+ crc = crc ^ (crc >> 16)
+ res = s
+ l = len(letters)
+ for i in (0,1):
+ b = crc & 0xff
+ pos = (b // l) ^ (b % l)
+ res += letters[pos%l]
+ crc >>= 8
+
+ return res
+
+
+def pidFromSerial(s, l):
+ crc = crc32(s)
+
+ arr1 = [0]*l
+ for i in xrange(len(s)):
+ arr1[i%l] ^= ord(s[i])
+
+ crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
+ for i in xrange(l):
+ arr1[i] ^= crc_bytes[i&3]
+
+ pid = ""
+ for i in xrange(l):
+ b = arr1[i] & 0xff
+ pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
+
+ return pid
+
+def main(argv=sys.argv):
+ print "Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky"
+ if len(sys.argv)==2:
+ serial = sys.argv[1]
+ else:
+ print "Usage: kindlepid.py /"
+ return 1
+ if len(serial)==16:
+ if serial.startswith("B"):
+ print "Kindle serial number detected"
+ else:
+ print "Warning: unrecognized serial number. Please recheck input."
+ return 1
+ pid = pidFromSerial(serial,7)+"*"
+ print "Mobipocket PID for Kindle serial# "+serial+" is "+checksumPid(pid)
+ return 0
+ elif len(serial)==40:
+ print "iPhone serial number (UDID) detected"
+ pid = pidFromSerial(serial,8)
+ print "Mobipocket PID for iPhone serial# "+serial+" is "+checksumPid(pid)
+ return 0
+ else:
+ print "Warning: unrecognized serial number. Please recheck input."
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto.dylib b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto.dylib
new file mode 100755
index 0000000..01c348c
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto.dylib differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto32.so b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto32.so
new file mode 100755
index 0000000..9a5a442
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto32.so differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto64.so b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto64.so
new file mode 100755
index 0000000..a08ac28
Binary files /dev/null and b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/libalfcrypto64.so differ
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/mobidedrm.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/mobidedrm.py
new file mode 100644
index 0000000..cd993e1
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/mobidedrm.py
@@ -0,0 +1,460 @@
+#!/usr/bin/python
+#
+# This is a python script. You need a Python interpreter to run it.
+# For example, ActiveState Python, which exists for windows.
+#
+# Changelog
+# 0.01 - Initial version
+# 0.02 - Huffdic compressed books were not properly decrypted
+# 0.03 - Wasn't checking MOBI header length
+# 0.04 - Wasn't sanity checking size of data record
+# 0.05 - It seems that the extra data flags take two bytes not four
+# 0.06 - And that low bit does mean something after all :-)
+# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
+# 0.08 - ...and also not in Mobi header version < 6
+# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
+# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
+# import filter it works when importing unencrypted files.
+# Also now handles encrypted files that don't need a specific PID.
+# 0.11 - use autoflushed stdout and proper return values
+# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors
+# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace
+# and extra blank lines, converted CR/LF pairs at ends of each line,
+# and other cosmetic fixes.
+# 0.14 - Working out when the extra data flags are present has been problematic
+# Versions 7 through 9 have tried to tweak the conditions, but have been
+# only partially successful. Closer examination of lots of sample
+# files reveals that a confusion has arisen because trailing data entries
+# are not encrypted, but it turns out that the multibyte entries
+# in utf8 file are encrypted. (Although neither kind gets compressed.)
+# This knowledge leads to a simplification of the test for the
+# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
+# 0.15 - Now outputs 'heartbeat', and is also quicker for long files.
+# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
+# 0.17 - added modifications to support its use as an imported python module
+# both inside calibre and also in other places (ie K4DeDRM tools)
+# 0.17a- disabled the standalone plugin feature since a plugin can not import
+# a plugin
+# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
+# Removed the disabled Calibre plug-in code
+# Permit use of 8-digit PIDs
+# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
+# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
+# 0.21 - Added support for multiple pids
+# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
+# 0.23 - fixed problem with older files with no EXTH section
+# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
+# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
+# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
+# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
+# 0.28 - slight additional changes to metadata token generation (None -> '')
+# 0.29 - It seems that the ideas about when multibyte trailing characters were
+# included in the encryption were wrong. They are for DOC compressed
+# files, but they are not for HUFF/CDIC compress files!
+# 0.30 - Modified interface slightly to work better with new calibre plugin style
+# 0.31 - The multibyte encrytion info is true for version 7 files too.
+# 0.32 - Added support for "Print Replica" Kindle ebooks
+# 0.33 - Performance improvements for large files (concatenation)
+# 0.34 - Performance improvements in decryption (libalfcrypto)
+# 0.35 - add interface to get mobi_version
+# 0.36 - fixed problem with TEXtREAd and getBookTitle interface
+# 0.37 - Fixed double announcement for stand-alone operation
+
+
+__version__ = '0.37'
+
+import sys
+
+class Unbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ def write(self, data):
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+sys.stdout=Unbuffered(sys.stdout)
+
+import os
+import struct
+import binascii
+from alfcrypto import Pukall_Cipher
+
+class DrmException(Exception):
+ pass
+
+
+#
+# MobiBook Utility Routines
+#
+
+# Implementation of Pukall Cipher 1
+def PC1(key, src, decryption=True):
+ return Pukall_Cipher().PC1(key,src,decryption)
+# sum1 = 0;
+# sum2 = 0;
+# keyXorVal = 0;
+# if len(key)!=16:
+# print "Bad key length!"
+# return None
+# wkey = []
+# for i in xrange(8):
+# wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
+# dst = ""
+# for i in xrange(len(src)):
+# temp1 = 0;
+# byteXorVal = 0;
+# for j in xrange(8):
+# temp1 ^= wkey[j]
+# sum2 = (sum2+j)*20021 + sum1
+# sum1 = (temp1*346)&0xFFFF
+# sum2 = (sum2+sum1)&0xFFFF
+# temp1 = (temp1*20021+1)&0xFFFF
+# byteXorVal ^= temp1 ^ sum2
+# curByte = ord(src[i])
+# if not decryption:
+# keyXorVal = curByte * 257;
+# curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
+# if decryption:
+# keyXorVal = curByte * 257;
+# for j in xrange(8):
+# wkey[j] ^= keyXorVal;
+# dst+=chr(curByte)
+# return dst
+
+def checksumPid(s):
+ letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+ crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
+ crc = crc ^ (crc >> 16)
+ res = s
+ l = len(letters)
+ for i in (0,1):
+ b = crc & 0xff
+ pos = (b // l) ^ (b % l)
+ res += letters[pos%l]
+ crc >>= 8
+ return res
+
+def getSizeOfTrailingDataEntries(ptr, size, flags):
+ def getSizeOfTrailingDataEntry(ptr, size):
+ bitpos, result = 0, 0
+ if size <= 0:
+ return result
+ while True:
+ v = ord(ptr[size-1])
+ result |= (v & 0x7F) << bitpos
+ bitpos += 7
+ size -= 1
+ if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
+ return result
+ num = 0
+ testflags = flags >> 1
+ while testflags:
+ if testflags & 1:
+ num += getSizeOfTrailingDataEntry(ptr, size - num)
+ testflags >>= 1
+ # Check the low bit to see if there's multibyte data present.
+ # if multibyte data is included in the encryped data, we'll
+ # have already cleared this flag.
+ if flags & 1:
+ num += (ord(ptr[size - num - 1]) & 0x3) + 1
+ return num
+
+
+
+class MobiBook:
+ def loadSection(self, section):
+ if (section + 1 == self.num_sections):
+ endoff = len(self.data_file)
+ else:
+ endoff = self.sections[section + 1][0]
+ off = self.sections[section][0]
+ return self.data_file[off:endoff]
+
+ def __init__(self, infile, announce = True):
+ if announce:
+ print ('MobiDeDrm v%(__version__)s. '
+ 'Copyright 2008-2012 The Dark Reverser et al.' % globals())
+
+ # initial sanity check on file
+ self.data_file = file(infile, 'rb').read()
+ self.mobi_data = ''
+ self.header = self.data_file[0:78]
+ if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
+ raise DrmException("invalid file format")
+ self.magic = self.header[0x3C:0x3C+8]
+ self.crypto_type = -1
+
+ # build up section offset and flag info
+ self.num_sections, = struct.unpack('>H', self.header[76:78])
+ self.sections = []
+ for i in xrange(self.num_sections):
+ offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
+ flags, val = a1, a2<<16|a3<<8|a4
+ self.sections.append( (offset, flags, val) )
+
+ # parse information from section 0
+ self.sect = self.loadSection(0)
+ self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
+ self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
+
+ if self.magic == 'TEXtREAd':
+ print "Book has format: ", self.magic
+ self.extra_data_flags = 0
+ self.mobi_length = 0
+ self.mobi_codepage = 1252
+ self.mobi_version = -1
+ self.meta_array = {}
+ return
+ self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
+ self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
+ self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
+ print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
+ self.extra_data_flags = 0
+ if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
+ self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
+ print "Extra Data Flags = %d" % self.extra_data_flags
+ if (self.compression != 17480):
+ # multibyte utf8 data is included in the encryption for PalmDoc compression
+ # so clear that byte so that we leave it to be decrypted.
+ self.extra_data_flags &= 0xFFFE
+
+ # if exth region exists parse it for metadata array
+ self.meta_array = {}
+ try:
+ exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
+ exth = 'NONE'
+ if exth_flag & 0x40:
+ exth = self.sect[16 + self.mobi_length:]
+ if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
+ nitems, = struct.unpack('>I', exth[8:12])
+ pos = 12
+ for i in xrange(nitems):
+ type, size = struct.unpack('>II', exth[pos: pos + 8])
+ content = exth[pos + 8: pos + size]
+ self.meta_array[type] = content
+ # reset the text to speech flag and clipping limit, if present
+ if type == 401 and size == 9:
+ # set clipping limit to 100%
+ self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
+ elif type == 404 and size == 9:
+ # make sure text to speech is enabled
+ self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
+ # print type, size, content, content.encode('hex')
+ pos += size
+ except:
+ self.meta_array = {}
+ pass
+ self.print_replica = False
+
+ def getBookTitle(self):
+ codec_map = {
+ 1252 : 'windows-1252',
+ 65001 : 'utf-8',
+ }
+ title = ''
+ codec = 'windows-1252'
+ if self.magic == 'BOOKMOBI':
+ if 503 in self.meta_array:
+ title = self.meta_array[503]
+ else:
+ toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
+ tend = toff + tlen
+ title = self.sect[toff:tend]
+ if self.mobi_codepage in codec_map.keys():
+ codec = codec_map[self.mobi_codepage]
+ if title == '':
+ title = self.header[:32]
+ title = title.split("\0")[0]
+ return unicode(title, codec).encode('utf-8')
+
+ def getPIDMetaInfo(self):
+ rec209 = ''
+ token = ''
+ if 209 in self.meta_array:
+ rec209 = self.meta_array[209]
+ data = rec209
+ # The 209 data comes in five byte groups. Interpret the last four bytes
+ # of each group as a big endian unsigned integer to get a key value
+ # if that key exists in the meta_array, append its contents to the token
+ for i in xrange(0,len(data),5):
+ val, = struct.unpack('>I',data[i+1:i+5])
+ sval = self.meta_array.get(val,'')
+ token += sval
+ return rec209, token
+
+ def patch(self, off, new):
+ self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
+
+ def patchSection(self, section, new, in_off = 0):
+ if (section + 1 == self.num_sections):
+ endoff = len(self.data_file)
+ else:
+ endoff = self.sections[section + 1][0]
+ off = self.sections[section][0]
+ assert off + in_off + len(new) <= endoff
+ self.patch(off + in_off, new)
+
+ def parseDRM(self, data, count, pidlist):
+ found_key = None
+ keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
+ for pid in pidlist:
+ bigpid = pid.ljust(16,'\0')
+ temp_key = PC1(keyvec1, bigpid, False)
+ temp_key_sum = sum(map(ord,temp_key)) & 0xff
+ found_key = None
+ for i in xrange(count):
+ verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
+ if cksum == temp_key_sum:
+ cookie = PC1(temp_key, cookie)
+ ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
+ if verification == ver and (flags & 0x1F) == 1:
+ found_key = finalkey
+ break
+ if found_key != None:
+ break
+ if not found_key:
+ # Then try the default encoding that doesn't require a PID
+ pid = "00000000"
+ temp_key = keyvec1
+ temp_key_sum = sum(map(ord,temp_key)) & 0xff
+ for i in xrange(count):
+ verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
+ if cksum == temp_key_sum:
+ cookie = PC1(temp_key, cookie)
+ ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
+ if verification == ver:
+ found_key = finalkey
+ break
+ return [found_key,pid]
+
+ def getMobiFile(self, outpath):
+ file(outpath,'wb').write(self.mobi_data)
+
+ def getMobiVersion(self):
+ return self.mobi_version
+
+ def getPrintReplica(self):
+ return self.print_replica
+
+ def processBook(self, pidlist):
+ crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
+ print 'Crypto Type is: ', crypto_type
+ self.crypto_type = crypto_type
+ if crypto_type == 0:
+ print "This book is not encrypted."
+ # we must still check for Print Replica
+ self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
+ self.mobi_data = self.data_file
+ return
+ if crypto_type != 2 and crypto_type != 1:
+ raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
+ if 406 in self.meta_array:
+ data406 = self.meta_array[406]
+ val406, = struct.unpack('>Q',data406)
+ if val406 != 0:
+ raise DrmException("Cannot decode library or rented ebooks.")
+
+ goodpids = []
+ for pid in pidlist:
+ if len(pid)==10:
+ if checksumPid(pid[0:-2]) != pid:
+ print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
+ goodpids.append(pid[0:-2])
+ elif len(pid)==8:
+ goodpids.append(pid)
+
+ if self.crypto_type == 1:
+ t1_keyvec = "QDCVEPMU675RUBSZ"
+ if self.magic == 'TEXtREAd':
+ bookkey_data = self.sect[0x0E:0x0E+16]
+ elif self.mobi_version < 0:
+ bookkey_data = self.sect[0x90:0x90+16]
+ else:
+ bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
+ pid = "00000000"
+ found_key = PC1(t1_keyvec, bookkey_data)
+ else :
+ # calculate the keys
+ drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
+ if drm_count == 0:
+ raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
+ found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
+ if not found_key:
+ raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.")
+ # kill the drm keys
+ self.patchSection(0, "\0" * drm_size, drm_ptr)
+ # kill the drm pointers
+ self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
+
+ if pid=="00000000":
+ print "File has default encryption, no specific PID."
+ else:
+ print "File is encoded with PID "+checksumPid(pid)+"."
+
+ # clear the crypto type
+ self.patchSection(0, "\0" * 2, 0xC)
+
+ # decrypt sections
+ print "Decrypting. Please wait . . .",
+ mobidataList = []
+ mobidataList.append(self.data_file[:self.sections[1][0]])
+ for i in xrange(1, self.records+1):
+ data = self.loadSection(i)
+ extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
+ if i%100 == 0:
+ print ".",
+ # print "record %d, extra_size %d" %(i,extra_size)
+ decoded_data = PC1(found_key, data[0:len(data) - extra_size])
+ if i==1:
+ self.print_replica = (decoded_data[0:4] == '%MOP')
+ mobidataList.append(decoded_data)
+ if extra_size > 0:
+ mobidataList.append(data[-extra_size:])
+ if self.num_sections > self.records+1:
+ mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
+ self.mobi_data = "".join(mobidataList)
+ print "done"
+ return
+
+def getUnencryptedBook(infile,pid,announce=True):
+ if not os.path.isfile(infile):
+ raise DrmException('Input File Not Found')
+ book = MobiBook(infile,announce)
+ book.processBook([pid])
+ return book.mobi_data
+
+def getUnencryptedBookWithList(infile,pidlist,announce=True):
+ if not os.path.isfile(infile):
+ raise DrmException('Input File Not Found')
+ book = MobiBook(infile, announce)
+ book.processBook(pidlist)
+ return book.mobi_data
+
+
+def main(argv=sys.argv):
+ print ('MobiDeDrm v%(__version__)s. '
+ 'Copyright 2008-2012 The Dark Reverser et al.' % globals())
+ if len(argv)<3 or len(argv)>4:
+ print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
+ print "Usage:"
+ print " %s []" % sys.argv[0]
+ return 1
+ else:
+ infile = argv[1]
+ outfile = argv[2]
+ if len(argv) is 4:
+ pidlist = argv[3].split(',')
+ else:
+ pidlist = {}
+ try:
+ stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
+ file(outfile, 'wb').write(stripped_file)
+ except DrmException, e:
+ print "Error: %s" % e
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/openssl_des.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/openssl_des.py
new file mode 100755
index 0000000..a4a40ca
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/openssl_des.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+# implement just enough of des from openssl to make erdr2pml.py happy
+
+def load_libcrypto():
+ from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
+ Structure, c_ulong, create_string_buffer, cast
+ from ctypes.util import find_library
+ import sys
+
+ if sys.platform.startswith('win'):
+ libcrypto = find_library('libeay32')
+ else:
+ libcrypto = find_library('crypto')
+
+ if libcrypto is None:
+ return None
+
+ libcrypto = CDLL(libcrypto)
+
+ # typedef struct DES_ks
+ # {
+ # union
+ # {
+ # DES_cblock cblock;
+ # /* make sure things are correct size on machines with
+ # * 8 byte longs */
+ # DES_LONG deslong[2];
+ # } ks[16];
+ # } DES_key_schedule;
+
+ # just create a big enough place to hold everything
+ # it will have alignment of structure so we should be okay (16 byte aligned?)
+ class DES_KEY_SCHEDULE(Structure):
+ _fields_ = [('DES_cblock1', c_char * 16),
+ ('DES_cblock2', c_char * 16),
+ ('DES_cblock3', c_char * 16),
+ ('DES_cblock4', c_char * 16),
+ ('DES_cblock5', c_char * 16),
+ ('DES_cblock6', c_char * 16),
+ ('DES_cblock7', c_char * 16),
+ ('DES_cblock8', c_char * 16),
+ ('DES_cblock9', c_char * 16),
+ ('DES_cblock10', c_char * 16),
+ ('DES_cblock11', c_char * 16),
+ ('DES_cblock12', c_char * 16),
+ ('DES_cblock13', c_char * 16),
+ ('DES_cblock14', c_char * 16),
+ ('DES_cblock15', c_char * 16),
+ ('DES_cblock16', c_char * 16)]
+
+ DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
+
+ def F(restype, name, argtypes):
+ func = getattr(libcrypto, name)
+ func.restype = restype
+ func.argtypes = argtypes
+ return func
+
+ DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
+ DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
+
+
+ class DES(object):
+ def __init__(self, key):
+ if len(key) != 8 :
+ raise Error('DES improper key used')
+ return
+ self.key = key
+ self.keyschedule = DES_KEY_SCHEDULE()
+ DES_set_key(self.key, self.keyschedule)
+ def desdecrypt(self, data):
+ ob = create_string_buffer(len(data))
+ DES_ecb_encrypt(data, ob, self.keyschedule, 0)
+ return ob.raw
+ def decrypt(self, data):
+ if not data:
+ return ''
+ i = 0
+ result = []
+ while i < len(data):
+ block = data[i:i+8]
+ processed_block = self.desdecrypt(block)
+ result.append(processed_block)
+ i += 8
+ return ''.join(result)
+
+ return DES
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/pycrypto_des.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/pycrypto_des.py
new file mode 100644
index 0000000..80d7d65
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/pycrypto_des.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+
+def load_pycrypto():
+ try :
+ from Crypto.Cipher import DES as _DES
+ except:
+ return None
+
+ class DES(object):
+ def __init__(self, key):
+ if len(key) != 8 :
+ raise Error('DES improper key used')
+ self.key = key
+ self._des = _DES.new(key,_DES.MODE_ECB)
+ def desdecrypt(self, data):
+ return self._des.decrypt(data)
+ def decrypt(self, data):
+ if not data:
+ return ''
+ i = 0
+ result = []
+ while i < len(data):
+ block = data[i:i+8]
+ processed_block = self.desdecrypt(block)
+ result.append(processed_block)
+ i += 8
+ return ''.join(result)
+ return DES
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/python_des.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/python_des.py
new file mode 100644
index 0000000..bd02904
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/python_des.py
@@ -0,0 +1,220 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+import sys
+
+ECB = 0
+CBC = 1
+class Des(object):
+ __pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
+ 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
+ 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
+ 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
+ __left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
+ __pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
+ 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
+ 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
+ 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
+ __ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
+ 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
+ 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
+ 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
+ __expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
+ 7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
+ 15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
+ 23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]
+ __sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
+ 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
+ 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
+ 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
+ [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
+ 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
+ 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
+ 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
+ [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
+ 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
+ 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
+ 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
+ [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
+ 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
+ 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
+ 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
+ [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
+ 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
+ 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
+ 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
+ [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
+ 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
+ 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
+ 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
+ [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
+ 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
+ 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
+ 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
+ [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
+ 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
+ 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
+ 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
+ __p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
+ 4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
+ __fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30,
+ 37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28,
+ 35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
+ 33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
+ # Type of crypting being done
+ ENCRYPT = 0x00
+ DECRYPT = 0x01
+ def __init__(self, key, mode=ECB, IV=None):
+ if len(key) != 8:
+ raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
+ self.block_size = 8
+ self.key_size = 8
+ self.__padding = ''
+ self.setMode(mode)
+ if IV:
+ self.setIV(IV)
+ self.L = []
+ self.R = []
+ self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
+ self.final = []
+ self.setKey(key)
+ def getKey(self):
+ return self.__key
+ def setKey(self, key):
+ self.__key = key
+ self.__create_sub_keys()
+ def getMode(self):
+ return self.__mode
+ def setMode(self, mode):
+ self.__mode = mode
+ def getIV(self):
+ return self.__iv
+ def setIV(self, IV):
+ if not IV or len(IV) != self.block_size:
+ raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
+ self.__iv = IV
+ def getPadding(self):
+ return self.__padding
+ def __String_to_BitList(self, data):
+ l = len(data) * 8
+ result = [0] * l
+ pos = 0
+ for c in data:
+ i = 7
+ ch = ord(c)
+ while i >= 0:
+ if ch & (1 << i) != 0:
+ result[pos] = 1
+ else:
+ result[pos] = 0
+ pos += 1
+ i -= 1
+ return result
+ def __BitList_to_String(self, data):
+ result = ''
+ pos = 0
+ c = 0
+ while pos < len(data):
+ c += data[pos] << (7 - (pos % 8))
+ if (pos % 8) == 7:
+ result += chr(c)
+ c = 0
+ pos += 1
+ return result
+ def __permutate(self, table, block):
+ return [block[x] for x in table]
+ def __create_sub_keys(self):
+ key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
+ i = 0
+ self.L = key[:28]
+ self.R = key[28:]
+ while i < 16:
+ j = 0
+ while j < Des.__left_rotations[i]:
+ self.L.append(self.L[0])
+ del self.L[0]
+ self.R.append(self.R[0])
+ del self.R[0]
+ j += 1
+ self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
+ i += 1
+ def __des_crypt(self, block, crypt_type):
+ block = self.__permutate(Des.__ip, block)
+ self.L = block[:32]
+ self.R = block[32:]
+ if crypt_type == Des.ENCRYPT:
+ iteration = 0
+ iteration_adjustment = 1
+ else:
+ iteration = 15
+ iteration_adjustment = -1
+ i = 0
+ while i < 16:
+ tempR = self.R[:]
+ self.R = self.__permutate(Des.__expansion_table, self.R)
+ self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
+ B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
+ j = 0
+ Bn = [0] * 32
+ pos = 0
+ while j < 8:
+ m = (B[j][0] << 1) + B[j][5]
+ n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
+ v = Des.__sbox[j][(m << 4) + n]
+ Bn[pos] = (v & 8) >> 3
+ Bn[pos + 1] = (v & 4) >> 2
+ Bn[pos + 2] = (v & 2) >> 1
+ Bn[pos + 3] = v & 1
+ pos += 4
+ j += 1
+ self.R = self.__permutate(Des.__p, Bn)
+ self.R = [x ^ y for x, y in zip(self.R, self.L)]
+ self.L = tempR
+ i += 1
+ iteration += iteration_adjustment
+ self.final = self.__permutate(Des.__fp, self.R + self.L)
+ return self.final
+ def crypt(self, data, crypt_type):
+ if not data:
+ return ''
+ if len(data) % self.block_size != 0:
+ if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
+ raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
+ if not self.getPadding():
+ raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
+ else:
+ data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
+ if self.getMode() == CBC:
+ if self.getIV():
+ iv = self.__String_to_BitList(self.getIV())
+ else:
+ raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
+ i = 0
+ dict = {}
+ result = []
+ while i < len(data):
+ block = self.__String_to_BitList(data[i:i+8])
+ if self.getMode() == CBC:
+ if crypt_type == Des.ENCRYPT:
+ block = [x ^ y for x, y in zip(block, iv)]
+ processed_block = self.__des_crypt(block, crypt_type)
+ if crypt_type == Des.DECRYPT:
+ processed_block = [x ^ y for x, y in zip(processed_block, iv)]
+ iv = block
+ else:
+ iv = processed_block
+ else:
+ processed_block = self.__des_crypt(block, crypt_type)
+ result.append(self.__BitList_to_String(processed_block))
+ i += 8
+ if crypt_type == Des.DECRYPT and self.getPadding():
+ s = result[-1]
+ while s[-1] == self.getPadding():
+ s = s[:-1]
+ result[-1] = s
+ return ''.join(result)
+ def encrypt(self, data, pad=''):
+ self.__padding = pad
+ return self.crypt(data, Des.ENCRYPT)
+ def decrypt(self, data, pad=''):
+ self.__padding = pad
+ return self.crypt(data, Des.DECRYPT)
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/scrolltextwidget.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/scrolltextwidget.py
new file mode 100755
index 0000000..98b4147
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/scrolltextwidget.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+
+import Tkinter
+import Tkconstants
+
+# basic scrolled text widget
+class ScrolledText(Tkinter.Text):
+ def __init__(self, master=None, **kw):
+ self.frame = Tkinter.Frame(master)
+ self.vbar = Tkinter.Scrollbar(self.frame)
+ self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y)
+ kw.update({'yscrollcommand': self.vbar.set})
+ Tkinter.Text.__init__(self, self.frame, **kw)
+ self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True)
+ self.vbar['command'] = self.yview
+ # Copy geometry methods of self.frame without overriding Text
+ # methods = hack!
+ text_meths = vars(Tkinter.Text).keys()
+ methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys()
+ methods = set(methods).difference(text_meths)
+ for m in methods:
+ if m[0] != '_' and m != 'config' and m != 'configure':
+ setattr(self, m, getattr(self.frame, m))
+
+ def __str__(self):
+ return str(self.frame)
diff --git a/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/stylexml2css.py b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/stylexml2css.py
new file mode 100755
index 0000000..2347f6a
--- /dev/null
+++ b/DeDRM_Applications/Macintosh/DeDRM 5.4.1.app/Contents/Resources/stylexml2css.py
@@ -0,0 +1,266 @@
+#! /usr/bin/python
+# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+# For use with Topaz Scripts Version 2.6
+
+import csv
+import sys
+import os
+import getopt
+import re
+from struct import pack
+from struct import unpack
+
+
+class DocParser(object):
+ def __init__(self, flatxml, fontsize, ph, pw):
+ self.flatdoc = flatxml.split('\n')
+ self.fontsize = int(fontsize)
+ self.ph = int(ph) * 1.0
+ self.pw = int(pw) * 1.0
+
+ stags = {
+ 'paragraph' : 'p',
+ 'graphic' : '.graphic'
+ }
+
+ attr_val_map = {
+ 'hang' : 'text-indent: ',
+ 'indent' : 'text-indent: ',
+ 'line-space' : 'line-height: ',
+ 'margin-bottom' : 'margin-bottom: ',
+ 'margin-left' : 'margin-left: ',
+ 'margin-right' : 'margin-right: ',
+ 'margin-top' : 'margin-top: ',
+ 'space-after' : 'padding-bottom: ',
+ }
+
+ attr_str_map = {
+ 'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
+ 'align-left' : 'text-align: left;',
+ 'align-right' : 'text-align: right;',
+ 'align-justify' : 'text-align: justify;',
+ 'display-inline' : 'display: inline;',
+ 'pos-left' : 'text-align: left;',
+ 'pos-right' : 'text-align: right;',
+ 'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
+ }
+
+
+ # find tag if within pos to end inclusive
+ def findinDoc(self, tagpath, pos, end) :
+ result = None
+ docList = self.flatdoc
+ cnt = len(docList)
+ if end == -1 :
+ end = cnt
+ else:
+ end = min(cnt,end)
+ foundat = -1
+ for j in xrange(pos, end):
+ item = docList[j]
+ if item.find('=') >= 0:
+ (name, argres) = item.split('=',1)
+ else :
+ name = item
+ argres = ''
+ if name.endswith(tagpath) :
+ result = argres
+ foundat = j
+ break
+ return foundat, result
+
+
+ # return list of start positions for the tagpath
+ def posinDoc(self, tagpath):
+ startpos = []
+ pos = 0
+ res = ""
+ while res != None :
+ (foundpos, res) = self.findinDoc(tagpath, pos, -1)
+ if res != None :
+ startpos.append(foundpos)
+ pos = foundpos + 1
+ return startpos
+
+ # returns a vector of integers for the tagpath
+ def getData(self, tagpath, pos, end, clean=False):
+ if clean:
+ digits_only = re.compile(r'''([0-9]+)''')
+ argres=[]
+ (foundat, argt) = self.findinDoc(tagpath, pos, end)
+ if (argt != None) and (len(argt) > 0) :
+ argList = argt.split('|')
+ for strval in argList:
+ if clean:
+ m = re.search(digits_only, strval)
+ if m != None:
+ strval = m.group()
+ argres.append(int(strval))
+ return argres
+
+ def process(self):
+
+ classlst = ''
+ csspage = '.cl-center { text-align: center; margin-left: auto; margin-right: auto; }\n'
+ csspage += '.cl-right { text-align: right; }\n'
+ csspage += '.cl-left { text-align: left; }\n'
+ csspage += '.cl-justify { text-align: justify; }\n'
+
+ # generate a list of each \n'
+ final += '\n\n'
+ in_tags = []
+ st_tags = []
+
+ def inSet(slist):
+ rval = False
+ j = len(in_tags)
+ if j == 0:
+ return False
+ while True:
+ j = j - 1
+ if in_tags[j][0] in slist:
+ rval = True
+ break
+ if j == 0:
+ break
+ return rval
+
+ def inBlock():
+ return inSet(self.html_block_tags)
+
+ def inLink():
+ return inSet(self.html_link_tags)
+
+ def inComment():
+ return inSet(self.html_comment_tags)
+
+ def inParaNow():
+ j = len(in_tags)
+ if j == 0:
+ return False
+ if in_tags[j-1][0] == 'P':
+ return True
+ return False
+
+ def getTag(ti, end):
+ cmd, attr = ti
+ r = self.html_tags[cmd][end]
+ if type(r) != str:
+ r = r(attr)
+ return r
+
+ def getSTag(ti, end):
+ cmd, attr = ti
+ r = self.html_style_tags[cmd][end]
+ if type(r) != str:
+ r = r(attr)
+ return r
+
+ def applyStyles(ending):
+ s = ''
+ j = len(st_tags)
+ if j > 0:
+ if ending:
+ while True:
+ j = j - 1
+ s += getSTag(st_tags[j], True)
+ if j == 0:
+ break
+ else:
+ k = 0
+ while True:
+ s += getSTag(st_tags[k], False)
+ k = k + 1
+ if k == j:
+ break
+ return s
+
+ def indentLevel(line_start):
+ nb = 0
+ while line_start[nb:nb+1] == ' ':
+ nb = nb + 1
+ line_start = line_start[nb:]
+ if nb > 5:
+ nb = 5
+ return nb, line_start
+
+
+ def makeText(s):
+ # handle replacements required for html
+ s = s.replace('&', '&')
+ s = s.replace('<', '<')
+ s = s.replace('>', '>')
+ return_s =''
+ # parse the text line by line
+ lp = s.find('\n')
+ while lp != -1:
+ line = s[0:lp]
+ s = s[lp+1:]
+ if not inBlock() and not inLink() and not inComment():
+ if len(line) > 0:
+ # text should not exist in the tag level unless it is in a comment
+ nb, line = indentLevel(line)
+ return_s += '' % nb
+ return_s += applyStyles(False)
+ return_s += line
+ return_s += applyStyles(True)
+ return_s += '
\n'
+ else:
+ return_s += '
\n'
+ elif inParaNow():
+ # text is a continuation of a previously started paragraph
+ return_s += line
+ return_s += applyStyles(True)
+ return_s += '\n'
+ j = len(in_tags)
+ del in_tags[j-1]
+ else:
+ if len(line) > 0:
+ return_s += line + ' \n'
+ else:
+ return_s += ' \n'
+ lp = s.find('\n')
+ linefrag = s
+ if len(linefrag) > 0:
+ if not inBlock() and not inLink() and not inComment():
+ nb, linefrag = indentLevel(linefrag)
+ return_s += '' % nb
+ return_s += applyStyles(False)
+ return_s += linefrag
+ ppair = ('P', None)
+ in_tags.append(ppair)
+ else:
+ return_s += linefrag
+ return return_s
+
+ while True:
+ r = self.next()
+ if not r:
+ break
+ text, cmd, attr = r
+
+ if text:
+ final += makeText(text)
+
+ if cmd:
+
+ # handle pseudo paragraph P tags
+ # close if starting a new block element
+ if cmd in self.html_block_tags or cmd == 'w':
+ j = len(in_tags)
+ if j > 0:
+ if in_tags[j-1][0] == 'P':
+ final += applyStyles(True)
+ final += getTag(in_tags[j-1],True)
+ del in_tags[j-1]
+
+ if cmd in self.html_block_tags:
+ pair = (cmd, attr)
+ if cmd not in [a for (a,b) in in_tags]:
+ # starting a new block tag
+ final += getTag(pair, False)
+ final += applyStyles(False)
+ in_tags.append(pair)
+ else:
+ # process ending tag for a tag pair
+ # ending tag should be for the most recently added start tag
+ j = len(in_tags)
+ if cmd == in_tags[j-1][0]:
+ final += applyStyles(True)
+ final += getTag(in_tags[j-1], True)
+ del in_tags[j-1]
+ else:
+ # ow: things are not properly nested
+ # process ending tag for block
+ # ending tag **should** be for the most recently added block tag
+ # but in too many cases it is not so we must fix this by
+ # closing all open tags up to the current one and then
+ # reopen all of the tags we had to close due to improper nesting of styles
+ print 'Warning: Improperly Nested Block Tags: expected %s found %s' % (cmd, in_tags[j-1][0])
+ print 'after processing %s' % final[-40:]
+ j = len(in_tags)
+ while True:
+ j = j - 1
+ final += applyStyles(True)
+ final += getTag(in_tags[j], True)
+ if in_tags[j][0] == cmd:
+ break
+ del in_tags[j]
+ # now create new block start tags if they were previously open
+ while j < len(st_tags):
+ final += getTag(in_tags[j], False)
+ final += applyStyles(False)
+ j = j + 1
+ self.skipNewLine()
+
+ elif cmd in self.html_link_tags:
+ pair = (cmd, attr)
+ if cmd not in [a for (a,b) in in_tags]:
+ # starting a new link tag
+ # first close out any still open styles
+ if inBlock():
+ final += applyStyles(True)
+ # output start tag and styles needed
+ final += getTag(pair, False)
+ final += applyStyles(False)
+ in_tags.append(pair)
+ else:
+ # process ending tag for a tag pair
+ # ending tag should be for the most recently added start tag
+ j = len(in_tags)
+ if cmd == in_tags[j-1][0]:
+ j = len(in_tags)
+ # apply closing styles and tag
+ final += applyStyles(True)
+ final += getTag(in_tags[j-1], True)
+ # if needed reopen any style tags
+ if inBlock():
+ final += applyStyles(False)
+ del in_tags[j-1]
+ else:
+ # ow: things are not properly nested
+ print 'Error: Improperly Nested Link Tags: expected %s found %s' % (cmd, in_tags[j-1][0])
+ print 'after processing %s' % final[-40:]
+
+ elif cmd in self.html_style_tags:
+ spair = (cmd, attr)
+ if cmd not in [a for (a,b) in st_tags]:
+ # starting a new style
+ if inBlock() or inLink():
+ final += getSTag(spair,False)
+ st_tags.append(spair)
+ else:
+ # process ending tag for style
+ # ending tag **should** be for the most recently added style tag
+ # but in too many cases it is not so we must fix this by
+ # closing all open tags up to the current one and then
+ # reopen all of the tags we had to close due to improper nesting of styles
+ j = len(st_tags)
+ while True:
+ j = j - 1
+ if inBlock() or inLink():
+ final += getSTag(st_tags[j], True)
+ if st_tags[j][0] == cmd:
+ break
+ del st_tags[j]
+ # now create new style start tags if they were previously open
+ while j < len(st_tags):
+ if inBlock() or inLink():
+ final += getSTag(st_tags[j], False)
+ j = j + 1
+
+ elif cmd in self.html_one_tags:
+ final += self.html_one_tags[cmd]
+
+ elif cmd == 'p':
+ # create page breaks at the
level so
+ # they can be easily used for safe html file segmentation breakpoints
+ # first close any open tags
+ j = len(in_tags)
+ if j > 0:
+ while True:
+ j = j - 1
+ if in_tags[j][0] in self.html_block_tags:
+ final += applyStyles(True)
+ final += getTag(in_tags[j], True)
+ if j == 0:
+ break
+
+ # insert the page break tag
+ final += '\n
\n'
+
+ if sigil_breaks:
+ if (len(final) - lastbreaksize) > 3000:
+ final += ' \n'
+ lastbreaksize = len(final)
+
+ # now create new start tags for all tags that
+ # were previously open
+ while j < len(in_tags):
+ final += getTag(in_tags[j], False)
+ if in_tags[j][0] in self.html_block_tags:
+ final += applyStyles(False)
+ j = j + 1
+ self.skipNewLine()
+
+ elif cmd[0:1] == 'C':
+ if self.markChapters:
+ # create toc entries at the level
+ # since they will be in an invisible block
+ # first close any open tags
+ j = len(in_tags)
+ if j > 0:
+ while True:
+ j = j - 1
+ if in_tags[j][0] in self.html_block_tags:
+ final += applyStyles(True)
+ final += getTag(in_tags[j], True)
+ if j == 0:
+ break
+ level = int(cmd[1:2]) + 1
+ final += ' ' % (level, attr, level)
+ # now create new start tags for all tags that
+ # were previously open
+ while j < len(in_tags):
+ final += getTag(in_tags[j], False)
+ if in_tags[j][0] in self.html_block_tags:
+ final += applyStyles(False)
+ j = j + 1
+ else:
+ final += '' % (cmd[1:2], attr)
+
+ # now handle single tags (non-paired) that have attributes
+ elif cmd == 'm':
+ unquotedimagepath = bookname + '_img/' + attr
+ imagepath = urllib.quote( unquotedimagepath )
+ final += ' ' % imagepath
+
+ elif cmd == 'Q':
+ final += ' ' % attr
+
+ elif cmd == 'a':
+ if not inBlock() and not inLink() and not inComment():
+ final += ''
+ final += applyStyles(False)
+ final += self.pml_chars.get(attr, '%d;' % attr)
+ ppair = ('P', None)
+ in_tags.append(ppair)
+ else:
+ final += self.pml_chars.get(attr, '%d;' % attr)
+
+ elif cmd == 'U':
+ if not inBlock() and not inLink() and not inComment():
+ final += '
'
+ final += applyStyles(False)
+ final += '%d;' % attr
+ ppair = ('P', None)
+ in_tags.append(ppair)
+ else:
+ final += makeText('%d;' % attr)
+
+ elif cmd == 'w':
+ # hr width and align parameters are not allowed in strict xhtml but style widths are possible
+ final += '\n
' % attr
+ # final += '
' % attr
+ self.skipNewLine()
+
+ elif cmd == 'T':
+ if inBlock() or inLink() or inComment():
+ final += ' ' % attr
+ else:
+ final += '' % attr
+ final += applyStyles(False)
+ ppair = ('P', None)
+ in_tags.append(ppair)
+
+ else:
+ logging.warning("Unknown tag: %s-%s", cmd, attr)
+
+
+ # handle file ending condition for imputed P tags
+ j = len(in_tags)
+ if (j > 0):
+ if in_tags[j-1][0] == 'P':
+ final += '
'
+
+ final += '\n\n'
+
+ # recode html back to a single slash
+ final = final.replace('_amp#92_', '\\')
+
+ # cleanup the html code for issues specifically generated by this translation process
+ # ending divs already break the line at the end so we don't need the we added
+ final = final.replace(' \n','\n')
+
+ # clean up empty elements that can be created when fixing improperly nested pml tags
+ # and by moving page break tags to the body level so that they can be used as html file split points
+ while True:
+ s = final
+ final = final.replace(' ','')
+ final = final.replace(' ','')
+ final = final.replace('','')
+ final = final.replace(' ','')
+ final = final.replace(' ','')
+ final = final.replace(' ','')
+ final = final.replace(' ','')
+ final = final.replace(' ','')
+ final = final.replace(' ','')
+ final = final.replace(' ','')
+ final = final.replace(' ','')
+ final = final.replace('
','')
+ final = final.replace('
','')
+ final = final.replace('
','')
+ final = final.replace('
','')
+ final = final.replace('
','')
+ final = final.replace('
','')
+ final = final.replace('
','')
+ final = final.replace('
','')
+ final = final.replace(' \n','')
+ final = final.replace(' \n','')
+ final = final.replace(' \n','')
+ final = final.replace(' \n','')
+ final = final.replace(' \n','')
+ final = final.replace('
\n','')
+ final = final.replace('
\n','')
+ final = final.replace('
\n','')
+ final = final.replace('
\n','')
+ if s == final:
+ break
+ return final
+
+
+def tidy(rawhtmlfile):
+ # processes rawhtmlfile through command line tidy via pipes
+ rawfobj = file(rawhtmlfile,'rb')
+ # --doctype strict forces strict dtd checking
+ # --enclose-text yes - enclosees non-block electment text inside into its own
block to meet xhtml spec
+ # -w 100 -i will wrap text at column 120 and indent it to indicate level of nesting to make structure clearer
+ # -win1252 sets the input encoding of pml files
+ # -asxhtml convert to xhtml
+ # -q (quiet)
+ cmdline = 'tidy -w 120 -i -q -asxhtml -win1252 --enclose-text yes --doctype strict '
+ if sys.platform[0:3] == 'win':
+ cmdline = 'tidy.exe -w 120 -i -q -asxhtml -win1252 --enclose-text yes --doctype strict '
+ p2 = Popen(cmdline, shell=True, stdin=rawfobj, stdout=PIPE, stderr=PIPE, close_fds=False)
+ stdout, stderr = p2.communicate()
+ # print "Tidy Original Conversion Warnings and Errors"
+ # print stderr
+ return stdout
+
+def usage():
+ print "Converts PML file to XHTML"
+ print "Usage:"
+ print " xpml2xhtml [options] infile.pml outfile.html "
+ print " "
+ print "Options: "
+ print " -h prints this message"
+ print " --sigil-breaks insert Sigil Chapterbbreaks"
+ print " --use-tidy use tidy to further clean up the html "
+ print " "
+ return
+
+def main(argv=None):
+ global bookname
+ global footnote_ids
+ global sidebar_ids
+ global sigil_breaks
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "h", ["sigil-breaks", "use-tidy"])
+ except getopt.GetoptError, err:
+ print str(err)
+ usage()
+ return 1
+ if len(args) != 2:
+ usage()
+ return 1
+ sigil_breaks = False
+ use_tidy = False
+ for o, a in opts:
+ if o == "-h":
+ usage()
+ return 0
+ elif o == "--sigil-breaks":
+ sigil_breaks = True
+ elif o == "--use-tidy":
+ use_tidy = True
+ infile, outfile = args[0], args[1]
+ bookname = os.path.splitext(os.path.basename(infile))[0]
+ footnote_ids = { }
+ sidebar_ids = { }
+ try:
+ print "Processing..."
+ import time
+ start_time = time.time()
+ print " Converting pml to raw html"
+ pml_string = file(infile,'rb').read()
+ pml = PmlConverter(pml_string)
+ html_src = pml.process()
+ if use_tidy:
+ print " Tidying html to xhtml"
+ fobj = tempfile.NamedTemporaryFile(mode='w+b',suffix=".html",delete=False)
+ tempname = fobj.name
+ fobj.write(html_src)
+ fobj.close()
+ html_src = tidy(tempname)
+ os.remove(tempname)
+ file(outfile,'wb').write(html_src)
+ end_time = time.time()
+ convert_time = end_time - start_time
+ print 'elapsed time: %.2f seconds' % (convert_time, )
+ print 'output is in file %s' % outfile
+ print "Finished Processing"
+ except ValueError, e:
+ print "Error: %s" % e
+ return 1
+ return 0
+
+if __name__ == "__main__":
+ #import cProfile
+ #command = """sys.exit(main())"""
+ #cProfile.runctx( command, globals(), locals(), filename="cprofile.profile" )
+
+ sys.exit(main())
diff --git a/Scuolabook_DRM/Scuolabook+DRM+Remover+1.0.zip b/Scuolabook_DRM/Scuolabook+DRM+Remover+1.0.zip
new file mode 100644
index 0000000..f2ee200
Binary files /dev/null and b/Scuolabook_DRM/Scuolabook+DRM+Remover+1.0.zip differ
diff --git a/Scuolabook_DRM/Scuolabook_DRM_Remover_source_19_11_2012.zip b/Scuolabook_DRM/Scuolabook_DRM_Remover_source_19_11_2012.zip
new file mode 100644
index 0000000..277458a
Binary files /dev/null and b/Scuolabook_DRM/Scuolabook_DRM_Remover_source_19_11_2012.zip differ
diff --git a/Scuolabook_DRM/Scuolabook_ReadMe.txt b/Scuolabook_DRM/Scuolabook_ReadMe.txt
new file mode 100755
index 0000000..f058a47
--- /dev/null
+++ b/Scuolabook_DRM/Scuolabook_ReadMe.txt
@@ -0,0 +1,10 @@
+How-to:
+1) Make sure you can read all PDF files on Scuolabook Reader.
+2) Run Scuolabook DRM Remover.
+3) Get your free books from the directory where you started the program.
+
+Note:
+It is recommended to use Scuolabook version 2.0.1 and refuse all updates
+because the encryption algorithm may change making this tool useless.
+
+Hex
\ No newline at end of file
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..1b9ff71
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,352 @@
+# DRM Removal Tools for eBooks v5.4.1
+This repository is a copy of the tools downloaded from [Apprentice Alf's Blog](http://www.apprenticealf.wordpress.com). I am not the author of these tools. I just wanted to keep them in a safe place. More info is at [Apprentice Alf's v5.4.1 blog post](http://apprenticealf.wordpress.com/2012/09/10/drm-removal-tools-for-ebooks/).
+
+# Notes
+- I've changed the ReadMe_First.txt to readme.md
+- Updated the readme.md with change notes and also citing the original author of the tools
+
+## Changes in 5.4.1:
+- Updated Kindle tools to fix a problem with long delays on some Mac systems
+- Updated ineptpdf and ignobleepub plugins
+- Added (Windows-only) Scuolabooks tool by Hex
+- Added B&N Download Helper by J-man
+- Updated Android Patch readme
+
+## Changes in 5.4a:
+- No changes to the tools.
+- Changed one folder name to no longer contain a colon (:)
+
+## Changes in 5.4:
+- Improved ReadMes, Improved dialogs, improved sanity-checking of configuration strings
+- Improved zipfix for ‘corrupt’ ePubs
+- Improved Amazon Topaz error reporting
+- Improved retrieval of decryption key from Kindle for PC
+- Improved reporting of decryption keys from Kindle for Mac
+- Improved ineptkey, fixing problem with PyCrypto implementation change
+- Improved ineptepub, fixing problem with PyCrypto implementation change
+- Improved ignobleepub, fixing problem with PyCrypto implementation change
+- Complete overhaul of ignobleepub plugin, now with secure configuration dialog
+- New patch for the latest Kindle for Android to allow display of device PID
+- In short, all plugins and DeDRM apps updated
+
+Welcome to the tools!
+=====================
+
+This readme.md is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v5.4.1 archive.
+
+The is archive includes tools to remove DRM from:
+
+ - Kindle ebooks (Mobi, Topaz, Print Replica and KF8).
+ - Barnes and Noble ePubs
+ - Adobe Digital Editions ePubs (including Sony and Kobo ePubs downloaded to ADE)
+ - Adobe Digital Editions PDFs
+ - Mobipocket ebooks
+ - eReader PDB books
+ - Scuolabooks (Windows only solution by Hex)
+
+These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.)
+
+
+About the tools
+---------------
+These tools have been updated and maintained by Apprentice Alf, DiapDealer and some_updates.
+
+You can find the latest updates and get support at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/
+
+If you re-post these tools, a link to the blog would be appreciated.
+
+The original inept and ignoble scripts were by I♥cabbages
+The original mobidedrm and erdr2pml scripts were by The Dark Reverser
+The original topaz DRM removal script was by CMBDTC
+The original topaz format conversion scripts were by some_updates, clarknova and Bart Simpson
+The Scuolabooks tool is by Hex
+
+The calibre plugin conversions were originally by DiapDealer
+The DeDRM AppleScript application was by Apprentice Alf
+The DeDRM python GUI was by some_updates
+
+Many fixes, updates and enhancements to the scripts and applicatons have been by Apprentice Alf, some_updates and DiapDealer.
+
+
+Calibre Users (Mac OS X, Windows, and Linux)
+--------------------------------------------
+If you are a calibre user, the quickest and easiest way, especially on Windows, to remove DRM from your ebooks is to install each of the plugins in the Calibre_Plugins folder, following the instructions and configuration directions provided in each plugin's ReadMe file.
+
+Once installed and configured, you can simply add a DRM book to calibre and the DeDRMed version will be imported into the calibre database. Note that DRM removal ONLY occurs on import. If you have already imported DRM books you'll need to remove them from calibre and re-import them.
+
+These plugins work for Windows, Mac OS X and Linux. For ebooks from Kindle 4 PC and Adobe Digital Editions, Linux users should read the section at the end of this ReadMe.
+
+
+
+DeDRM application for Mac OS X users: (Mac OS X 10.4 and above)
+----------------------------------------------------------------------
+This application combines all the tools into one easy-to-use tool for Mac OS X users.
+
+Drag the "DeDRM 5.4.1.app" application from the DeDRM_Applications/Macintosh folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and it will guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
+
+To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM application and it will remove the DRM of the kinds listed above.
+
+For more detailed instructions, see the "DeDRM ReadMe.rtf" file in the DeDRM_Applications/Macintosh folder, including details of the extra step that Mac OS X 10.4 users need to take to use the application.
+
+
+
+
+DeDRM application for Windows users: (Windows XP through Windows 7)
+------------------------------------------------------------------
+***This program requires that Python and PyCrypto be properly installed.***
+***See below for details on recommended versions are where to get them.***
+
+This application combines all the tools into one easy-to-use tool for Windows users.
+
+Drag the DeDRM_5.4.1 folder that's in the DeDRM_Applications/Windows folder, to your "My Documents" folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the DeDRM_Drop_Target.bat file that's in the DeDRM_5.4.1 folder. Double-click on the shortcut and the DeDRM application will run and guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
+
+To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM_Drop_Target.bat shortcut and it will remove the DRM of the kinds listed above.
+
+For more detailed instructions, see the DeDRM_ReadMe.txt file in the DeDRM_Applications/Windows folder.
+
+
+
+Other_Tools
+-----------
+This folder includes two non-python tools:
+
+Kindle_for_Android_Patches
+--------------------------
+Definitely only for the adventurous, this folder contains information on how to modify the Kindel for Android app to b able to get a PID for use with the other Kindle tools (DeDRM apps and calibre plugin).
+
+B&N_Download_Helper
+-------------------
+A Javascript to enable a download button at the B&N website for ebooks that normally won't download to your PC. Another one only for the adventurous.
+
+
+And then there are a number of other python based tools that have graphical user interfaces to make them easy to use. To use any of these tools, you need to have Python 2.5, 2.6, or 2.7 for 32 bits installed on your machine as well as a matching PyCrypto or OpenSSL for some tools.
+
+On Mac OS X (10.5, 10.6 and 10.7), your systems already have the proper Python and OpenSSL installed. So nothing need be done, you can already run these tools by double-clicking on the .pyw python scripts.
+
+Users of Mac OS X 10.3 and 10.4, need to download and install the "32-bit Mac Installer disk Image (2.7.3) for OS X 10.3 and later from http://www.python.org/ftp/python/2.7.3/python-2.7.3-macosx10.3.dmg.
+
+On Windows, you need to install a 32 bit version of Python (even on Windows 64) plus a matching 32 bit version of PyCrypto *OR* OpenSSL. We ***strongly*** recommend the free community edition of ActiveState's Active Python version. See the end of this document for details.
+
+Linux users should have python 2.7, and openssl installed, but may need to run some of these tools under recent versions of Wine. See the Linux_Users section below:
+
+The scripts in the Other_Tools folder are organized by type of ebook you need to remove the DRM from. Choose from among:
+
+ "Adobe_ePub_Tools"
+ "Adobe_PDF_Tools"
+ "Barnes_and_Noble_ePub_Tools"
+ "ePub_Fixer" (for fixing incorrectly made Adobe and Barnes and Noble ePubs)
+ "eReader_PDB_Tools"
+ "Kindle/Mobi_Tools"
+ "KindleBooks"
+
+by simply opening that folder.
+
+Look for a README inside of the relevant folder to get you started.
+
+
+
+Additional Tools
+----------------
+Some additional useful tools **unrelated to DRM** are also provided in the "Additional_Tools" folder inside the "Other_Tools" folder. There are tools for working with finding Topaz ebooks, unpacking Kindle/Mobipocket ebooks (without DRM) to get to the Mobipocket markup language inside, tools to strip source archive from Kindlegen generated mobis, tools to work with Kindle for iPhone/iPad, etc, and tools to dump the contents of mobi headers to see all EXTH (metadata) and related values.
+
+
+Scuolabook_DRM
+-------------
+This is a Windows-only tool produced by Hex and included with permission.
+
+
+Windows and Python
+------------------
+We **strongly** recommend ActiveState's Active Python 2.7 Community Edition for Windows (x86) 32 bits. This can be downloaded for free from:
+
+ http://www.activestate.com/activepython/downloads
+
+We do **NOT** recommend the version of Python from python.org.
+
+The version from python.org is not as complete as most normal Python installations on Linux and even Mac OS X. It is missing various Windows specific libraries, does not install the default Tk Widget kit (for graphical user interfaces) unless you select it as an option in the installer, and does not properly update the system PATH environment variable. Therefore using the default python.org build on Windows is simply an exercise in frustration for most Windows users.
+
+In addition, Windows Users need one of PyCrypto OR OpenSSL.
+
+For OpenSSL:
+
+ Win32 OpenSSL v0.9.8o (8Mb)
+ http://www.slproweb.com/download/Win32OpenSSL-0_9_8o.exe
+ (if you get an error message about missing Visual C++
+ redistributables... cancel the install and install the
+ below support program from Microsoft, THEN install OpenSSL)
+
+ Visual C++ 2008 Redistributables (1.7Mb)
+ http://www.microsoft.com/downloads/details.aspx?familyid=9B2DA534-3E03-4391-8A4D-074B9F2BC1BF
+
+For PyCrypto:
+
+ There are many places to get PyCrypto installers for Windows. One such place is:
+
+ http://www.voidspace.org.uk/python/modules.shtml
+
+ Please get the latest PyCrypto meant for Windows 32 bit that matches the version of Python you installed (2.7)
+
+Once Windows users have installed Python 2.X for 32 bits, and the matching OpenSSL OR PyCrypto pieces, they too are ready to run the scripts.
+
+
+
+
+
+Linux Users Only
+================
+
+Since Kindle for PC and Adobe Digital Editions do not offer native Linux versions, here are instructions for using Windows versions under Wine as well as related instructions for the special way to handle some of these tools:
+
+
+
+Linux and Kindle for PC
+-----------------------
+
+It is possible to run the Kindle for PC application under Wine.
+
+1. Install a recent version of Wine (>=1.3.15)
+
+2. Some versions of winecfg have a bug in setting the volume serial number, so create a .windows-serial file at root of drive_c to set a proper windows volume serial number (8 digit hex value for unsigned integer).
+cd ~
+cd .wine
+cd drive_c
+echo deadbeef > .windows-serial
+
+Replace "deadbeef" with whatever hex value you want but I would stay away from the default setting of "ffffffff" which does not seem to work. BTW: deadbeef is itself a valid possible hex value if you want to use it
+
+3. Download and install Kindle for PC under Wine.
+
+
+
+
+Linux and Kindle for PC (Other_Tools/KindleBooks/)
+--------------------------------------------------
+
+Here are the instructions for using Kindle for PC and KindleBooks.pyw on Linux under Wine. (Thank you Eyeless and Pete)
+
+1. upgrade to very recent versions of Wine; This has been tested with Wine 1.3.15 – 1.3.2X. It may work with earlier versions but no promises. It does not work with wine 1.2.X versions.
+
+If you have not already installed Kindle for PC under wine, follow steps 2 and 3 otherwise jump to step 4
+
+2. Some versions of winecfg have a bug in setting the volume serial number, so create a .windows-serial file at root of drive_c to set a proper windows volume serial number (8 digit hex value for unsigned integer).
+cd ~
+cd .wine
+cd drive_c
+echo deadbeef > .windows-serial
+
+Replace "deadbeef" with whatever hex value you want but I would stay away from the default setting of "ffffffff" which does not seem to work. BTW: deadbeef is itself a valid possible hex value if you want to use it
+
+3. Only ***after*** setting the volume serial number properly – download and install under wine K4PC version for Windows. Register it and download from your Archive one of your Kindle ebooks. Versions known to work are K4PC 1.7.1 and earlier. Later version may work but no promises.
+
+4. Download and install under wine ActiveState Active Python 2.7 for Windows 32bit
+
+5. Download and unzip tools_vX.X.zip
+
+6. Now make sure the executable bit is NOT set for KindleBooks.pyw as Linux will actually keep trying to ignore wine and launch it under Linux python which will cause it to fail.
+
+cd tools_vX.X/KindleBooks/
+chmod ugo-x KindleBooks.pyw
+
+7. Then run KindleBook.pyw ***under python running on wine*** using the Linux shell as follows:
+
+wine python KindleBooks.pyw
+
+Select the ebook file directly from your “My Kindle Content” folder, select a new/unused directory for the output. You should not need to enter any PID or Serial Number for Kindle for PC.
+
+
+
+
+Linux and Adobe Digital Editions ePubs
+--------------------------------------
+
+Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien!)
+
+
+1. download the most recent version of wine from winehq.org (1.3.29 in my case)
+
+For debian users:
+
+to get a recent version of wine I decited to use aptosid (2011-02, xfce)
+(because I’m used to debian)
+install aptosid and upgrade it (see aptosid site for detaild instructions)
+
+
+2. properly install Wine (see the Wine site for details)
+
+For debian users:
+
+cd to this dir and install the packages as root:
+‘dpkg -i *.deb’
+you will get some error messages, which can be ignored.
+again as root use
+‘apt-get -f install’ to correct this errors
+
+3. python 2.7 should already be installed on your system but you may need the following additional python package
+
+'apt-get install python-tk’
+
+4. all programms need to be installed as normal user. All these programm are installed the same way:
+‘wine ‘
+we need:
+a) Adobe Digital Edition 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html)
+(there is a “can’t install ADE” site, where the setup.exe hides)
+
+b) ActivePython-2.7.2.5-win32-x86.msi (from: http://www.activestate.com/activepython/downloads)
+
+c) Win32OpenSSL_Light-0_9_8r.exe (from: http://www.slproweb.com/)
+
+d) pycrypto-2.3.win32-py2.7.msi (from: http://www.voidspace.org.uk/python/modules.shtml)
+
+5. now get and unpack the very latest tools_vX.X (from Apprentice Alf) in the users drive_c of wine
+(~/.wine/drive_c/)
+
+6. start ADE with:
+‘wine digitaleditions.exe’ or from the start menue wine-adobe-digital..
+
+7. register this instance of ADE with your adobeID and close it
+ change to the tools_vX.X dir:
+cd ~/.wine/drive_c/tools_vX.X/Other_Tools/Adobe_ePub_Tools
+
+8. create the adeptkey.der with:
+‘wine python ineptkey_v5.4.1.pyw’ (only need once!)
+(key will be here: ~/.wine/drive_c/tools_v4.X/Other_Tools/Adobe_ePub_Tools/adeptkey.der)
+
+9. Use ADE running under Wine to dowload all of your purchased ePub ebooks
+
+10. for each book you have downloaded via Adobe Digital Editions
+There is no need to use Wine for this step!
+
+'python ineptpub_v5.6.pyw’
+this will launch a window with 3 lines
+1. key: (allready filled in, otherwise it’s in the path where you did step 8.
+2. input file: drmbook.epub
+3. output file: name-ypu-want_for_free_book.epub
+
+Also… once you successfully generate your adept.der keyfile using Wine, you can use the regular ineptepub plugin with the standard Linux calibre. Just put the *.der file(s) in your calibre configuration directory.
+so if you want you can use calibre in Linux:
+
+11. install the plugins from the tools as discribed in the readmes for win
+
+12. copy the adeptkey.der into the config dir of calibre (~/.config/calibre in debian). Every book imported to calibre will automaticly freed from DRM.
+
+
+Apple's iBooks FairPlay DRM
+---------------------------
+
+The only tool that removes Apple's iBooks Fairplay DRM that is Requiem by Brahms version 3.3 or later. Requiem is NOT included in this tools package. It is under active development because Apple constantly updates its DRM scheme to stop Requiem from working.
+The latest version as of October 2012 is 3.3.5 and works with iTunes 10.5 and above.
+
+Requiem has a Tor website: http://tag3ulp55xczs3pn.onion. To reach the site using Tor, you will need to install Tor (http://www.torproject.org). If you're willing to sacrifice your anonymity, you can use the regular web with tor2web. Just go to http://tag3ulp55xczs3pn.tor2web.com.
+
+Alternatively, you can download the 3.3.5 version from the following locationss:
+
+Requiem Windows application: http://www.datafilehost.com/download-b015485b.html
+MD5: 954f9ecf42635fae77afbc3a24489004
+
+Requiem Mac OS X application: http://www.datafilehost.com/download-50608ba6.html
+MD5: 4e7dc46ad7e0b54bea6182c5ad024ffe
+
+Requiem source code: http://www.datafilehost.com/download-af8f91a1.html
+MD5: e175560590a154859c0344e30870ac73
+
+No support for requiem is provided at Apprentice Alf's blog.