mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2025-10-23 23:07:47 -04:00
Compare commits
29 commits
autoreleas
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7379b45319 | ||
|
|
bde82fd7ab | ||
|
|
de3d91f5e5 | ||
|
|
c5ee327a60 | ||
|
|
501a1e6d31 | ||
|
|
815d86efe0 | ||
|
|
65646f4493 | ||
|
|
808dc7d29a | ||
|
|
2cd2792306 | ||
|
|
2e53d70e88 | ||
|
|
05fff5217b | ||
|
|
34c4c067e8 | ||
|
|
195ea69537 | ||
|
|
3373d93874 | ||
|
|
bf2471e65b | ||
|
|
5492dcdbf4 | ||
|
|
737d5e7f1e | ||
|
|
e4e5808894 | ||
|
|
ef67dbd204 | ||
|
|
10b6caf9f5 | ||
|
|
53996cf49c | ||
|
|
d388ae72fd | ||
|
|
bc089ee46d | ||
|
|
e509b7d520 | ||
|
|
e82d2b5c9c | ||
|
|
7f6dd84389 | ||
|
|
b9bad26d4b | ||
|
|
2a1413297e | ||
|
|
815f880e34 |
40 changed files with 762 additions and 636 deletions
8
.github/ISSUE_TEMPLATE/QUESTION.yml
vendored
8
.github/ISSUE_TEMPLATE/QUESTION.yml
vendored
|
|
@ -10,16 +10,16 @@ body:
|
||||||
id: calibre-version
|
id: calibre-version
|
||||||
attributes:
|
attributes:
|
||||||
label: Which version of Calibre are you running?
|
label: Which version of Calibre are you running?
|
||||||
description: "Example: 5.32"
|
description: "Example: 6.23"
|
||||||
placeholder: "5.32"
|
placeholder: "6.23"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
id: plugin-version
|
id: plugin-version
|
||||||
attributes:
|
attributes:
|
||||||
label: Which version of the DeDRM plugin are you running?
|
label: Which version of the DeDRM plugin are you running?
|
||||||
description: "Example: v10.0.0"
|
description: "Example: v10.0.2"
|
||||||
placeholder: "v10.0.0"
|
placeholder: "v10.0.2"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
|
|
||||||
44
.github/workflows/main.yml
vendored
44
.github/workflows/main.yml
vendored
|
|
@ -14,51 +14,39 @@ jobs:
|
||||||
run: python3 make_release.py
|
run: python3 make_release.py
|
||||||
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: plugin
|
name: plugin
|
||||||
path: |
|
path: |
|
||||||
DeDRM_tools_*.zip
|
DeDRM_tools_*.zip
|
||||||
DeDRM_tools.zip
|
DeDRM_tools.zip
|
||||||
|
|
||||||
- name: Delete old release
|
|
||||||
uses: cb80/delrel@latest
|
|
||||||
with:
|
|
||||||
tag: autorelease
|
|
||||||
token: ${{ github.token }}
|
|
||||||
|
|
||||||
- name: Delete old tag
|
|
||||||
uses: dev-drprasad/delete-tag-and-release@v1.0
|
|
||||||
with:
|
|
||||||
tag_name: autorelease
|
|
||||||
github_token: ${{ github.token }}
|
|
||||||
delete_release: true
|
|
||||||
|
|
||||||
- name: Prepare release
|
- name: Prepare release
|
||||||
run: cp DeDRM_tools.zip DeDRM_alpha_${{ github.sha }}.zip
|
run: cp DeDRM_tools.zip DeDRM_alpha_${{ github.sha }}.zip
|
||||||
|
|
||||||
|
|
||||||
|
- uses: dev-drprasad/delete-older-releases@v0.2.1
|
||||||
|
with:
|
||||||
|
repo: noDRM/DeDRM_tools_autorelease
|
||||||
|
keep_latest: 0
|
||||||
|
delete_tags: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.AUTORELEASE_KEY }}
|
||||||
|
|
||||||
- name: Auto-release
|
- name: Auto-release
|
||||||
id: autorelease
|
id: autorelease
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: autorelease
|
tag_name: autorelease_${{ github.sha }}
|
||||||
token: ${{ github.token }}
|
repository: noDRM/DeDRM_tools_autorelease
|
||||||
|
token: ${{ secrets.AUTORELEASE_KEY }}
|
||||||
name: Automatic alpha release with latest changes
|
name: Automatic alpha release with latest changes
|
||||||
body: |
|
body: |
|
||||||
This release is automatically generated by Github for each commit.
|
This release is automatically generated by Github for each commit.
|
||||||
|
|
||||||
This means, every time a change is made to this repo, this release will be updated to contain an untested copy of the plugin at that stage. This will contain the most up-to-date code, but it's not tested at all and may be broken.
|
This means, every time a change is made to the repo, a release with an untested copy of the plugin at that stage will be created. This will contain the most up-to-date code, but it's not tested at all and may be broken.
|
||||||
|
|
||||||
Last update based on Git commit ${{ github.sha }}.
|
Last update based on Git commit [${{ github.sha }}](https://github.com/noDRM/DeDRM_tools/commit/${{ github.sha }}).
|
||||||
prerelease: true
|
prerelease: true
|
||||||
draft: true
|
|
||||||
files: DeDRM_alpha_${{ github.sha }}.zip
|
|
||||||
|
|
||||||
- name: Make release public
|
|
||||||
uses: irongut/EditRelease@v1.2.0
|
|
||||||
with:
|
|
||||||
token: ${{ github.token }}
|
|
||||||
id: ${{ steps.autorelease.outputs.id }}
|
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: true
|
files: DeDRM_alpha_${{ github.sha }}.zip
|
||||||
|
|
||||||
|
|
|
||||||
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -67,7 +67,11 @@ List of changes since the fork of Apprentice Harper's repository:
|
||||||
- Fix Nook Study key retrieval code (partially fixes #50).
|
- Fix Nook Study key retrieval code (partially fixes #50).
|
||||||
- Make the plugin work on Calibre 6 (Qt 6). (fixes #54 and #98) If you're running Calibre 6 and you notice any issues, please open a bug report.
|
- Make the plugin work on Calibre 6 (Qt 6). (fixes #54 and #98) If you're running Calibre 6 and you notice any issues, please open a bug report.
|
||||||
|
|
||||||
## Fixes on master (not yet released):
|
## Fixes in v10.0.9 (RC for v10.1.0, 2023-08-02):
|
||||||
|
|
||||||
|
Note that versions v10.0.4(s), v10.0.5(s) and v10.0.6(s) were released by other people in various forks, so I have decided to make a larger version jump so there are no conflicting version numbers / different builds with the same version number.
|
||||||
|
|
||||||
|
This is v10.0.9, a release candidate for v10.1.0. I don't expect there to be major issues / bugs, but since a lot of code has changed in the last year I wanted to get some "extended testing" before this becomes v10.1.0.
|
||||||
|
|
||||||
- Fix a bug introduced with #48 that breaks DeDRM'ing on Calibre 4 (fixes #101).
|
- Fix a bug introduced with #48 that breaks DeDRM'ing on Calibre 4 (fixes #101).
|
||||||
- Fix some more Calibre-6 bugs in the Obok plugin (should fix #114).
|
- Fix some more Calibre-6 bugs in the Obok plugin (should fix #114).
|
||||||
|
|
@ -92,3 +96,18 @@ List of changes since the fork of Apprentice Harper's repository:
|
||||||
- Two bugfixes for Amazon DeDRM from Satuoni ( https://github.com/noDRM/DeDRM_tools/issues/315#issuecomment-1508305428 ) and andrewc12 ( https://github.com/andrewc12/DeDRM_tools/commit/d9233d61f00d4484235863969919059f4d0b2057 ) that might make the plugin work with newer versions.
|
- Two bugfixes for Amazon DeDRM from Satuoni ( https://github.com/noDRM/DeDRM_tools/issues/315#issuecomment-1508305428 ) and andrewc12 ( https://github.com/andrewc12/DeDRM_tools/commit/d9233d61f00d4484235863969919059f4d0b2057 ) that might make the plugin work with newer versions.
|
||||||
- Fix font decryption not working with some books (fixes #347), thanks for the patch @bydioeds.
|
- Fix font decryption not working with some books (fixes #347), thanks for the patch @bydioeds.
|
||||||
- Fix a couple unicode errors for Python2 in Kindle and Nook code.
|
- Fix a couple unicode errors for Python2 in Kindle and Nook code.
|
||||||
|
|
||||||
|
## Fixes on master (not yet released):
|
||||||
|
|
||||||
|
- Fix a bug where decrypting a 40-bit RC4 pdf with R=2 didn't work.
|
||||||
|
- Fix a bug where decrypting a 256-bit AES pdf with V=5 didn't work.
|
||||||
|
- Fix bugs in kgenpids.py, alfcrypto.py, mobidedrm.py and kindlekey.py that caused it to fail on Python 2 (#380).
|
||||||
|
- Fix some bugs (Python 2 and Python 3) in erdr2pml.py (untested).
|
||||||
|
- Fix file lock bug in androidkindlekey.py on Windows with Calibre >= 7 (untested).
|
||||||
|
- A bunch of updates to the external FileOpen ineptpdf script, might fix #442 (untested).
|
||||||
|
- Fix exception handling on decrypt in ion.py (#662, thanks @C0rn3j).
|
||||||
|
- Fix SHA1 hash function for erdr2pml.py script (#608, thanks @unwiredben).
|
||||||
|
- Make Kobo DRM removal not fail when there are undownloaded ebooks (#384, thanks @precondition).
|
||||||
|
- Fix Obok import failing in Calibre flatpak due to missing ip command (#586 and #585, thanks @jcotton42).
|
||||||
|
- Don't re-pack EPUB if there's no DRM to remove and no postprocessing done (fixes #555).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h1>DeDRM Plugin <span class="version">(v10.0.3)</span></h1>
|
<h1>DeDRM Plugin <span class="version">(v10.0.9 / v10.1.0 RC1)</span></h1>
|
||||||
|
|
||||||
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
||||||
|
|
||||||
|
|
@ -26,6 +26,8 @@ p {margin-top: 0}
|
||||||
<h3>Installation</h3>
|
<h3>Installation</h3>
|
||||||
<p>You have obviously managed to install the plugin, as otherwise you wouldn’t be reading this help file. However, you should also delete any older DRM removal plugins, as this DeDRM plugin replaces the five older plugins: Kindle and Mobipocket DeDRM (K4MobiDeDRM), Ignoble Epub DeDRM (ignobleepub), Inept Epub DeDRM (ineptepub), Inept PDF DeDRM (ineptepub) and eReader PDB 2 PML (eReaderPDB2PML).</p>
|
<p>You have obviously managed to install the plugin, as otherwise you wouldn’t be reading this help file. However, you should also delete any older DRM removal plugins, as this DeDRM plugin replaces the five older plugins: Kindle and Mobipocket DeDRM (K4MobiDeDRM), Ignoble Epub DeDRM (ignobleepub), Inept Epub DeDRM (ineptepub), Inept PDF DeDRM (ineptepub) and eReader PDB 2 PML (eReaderPDB2PML).</p>
|
||||||
|
|
||||||
|
<p>This plugin (in versions v10.0.0 and above) will automatically replace the older 7.X and below versions from Apprentice Alf and Apprentice Harper.</p>
|
||||||
|
|
||||||
<h3>Configuration</h3>
|
<h3>Configuration</h3>
|
||||||
<p>On Windows and Mac, the keys for ebooks downloaded for Kindle for Mac/PC and Adobe Digital Editions are automatically generated. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC and/or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. On Linux, keys for Kindle for PC and Adobe Digital Editions need to be generated separately (see the Linux section below).</p>
|
<p>On Windows and Mac, the keys for ebooks downloaded for Kindle for Mac/PC and Adobe Digital Editions are automatically generated. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC and/or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. On Linux, keys for Kindle for PC and Adobe Digital Editions need to be generated separately (see the Linux section below).</p>
|
||||||
|
|
||||||
|
|
@ -60,7 +62,7 @@ p {margin-top: 0}
|
||||||
<li>And probably many more.</li>
|
<li>And probably many more.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>For additional help read the <a href="https://github.com/noDRM/DeDRM_tools/blob/master/FAQs.md">FAQs</a> at <a href="https://github.com/noDRM/DeDRM_tools">NoDRM's GitHub repository</a> (or the corresponding <a href="https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md">FAQs</a> at <a href="https://github.com/apprenticeharper/DeDRM_tools/">Apprentice Harpers’s GitHub repository</a>). You can <a href="https://github.com/noDRM/DeDRM_tools/issues">open issue reports</a>related to this fork at NoDRM's GitHub repository.</h3>
|
<h4>For additional help read the <a href="https://github.com/noDRM/DeDRM_tools/blob/master/FAQs.md">FAQs</a> at <a href="https://github.com/noDRM/DeDRM_tools">NoDRM's GitHub repository</a> (or the corresponding <a href="https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md">FAQs</a> at <a href="https://github.com/apprenticeharper/DeDRM_tools/">Apprentice Harpers’s GitHub repository</a>). You can <a href="https://github.com/noDRM/DeDRM_tools/issues">open issue reports</a> related to this fork at NoDRM's GitHub repository.</h4>
|
||||||
|
|
||||||
|
|
||||||
<h2>Linux Systems Only</h2>
|
<h2>Linux Systems Only</h2>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ li {margin-top: 0.5em}
|
||||||
|
|
||||||
<p>If you have upgraded from an earlier version of the plugin, any existing Kindle for Mac/PC keys will have been automatically imported, so you might not need to do any more configuration. In addition, on Windows and Mac, the default Kindle for Mac/PC key is added the first time the plugin is run. Continue reading for key generation and management instructions.</p>
|
<p>If you have upgraded from an earlier version of the plugin, any existing Kindle for Mac/PC keys will have been automatically imported, so you might not need to do any more configuration. In addition, on Windows and Mac, the default Kindle for Mac/PC key is added the first time the plugin is run. Continue reading for key generation and management instructions.</p>
|
||||||
|
|
||||||
|
<p>Note that for best results, you should run Calibre / this plugin on the same machine where Kindle 4 PC / Kindle 4 Mac is running. It is possible to export/import the keys to another machine, but this may not always work, particularly with the newer DRM versions.</p>
|
||||||
|
|
||||||
<h3>Creating New Keys:</h3>
|
<h3>Creating New Keys:</h3>
|
||||||
|
|
||||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog prompting you to enter a key name for the default Kindle for Mac/PC key. </p>
|
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog prompting you to enter a key name for the default Kindle for Mac/PC key. </p>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ if "calibre" in sys.modules and sys.version_info[0] == 2:
|
||||||
if os.path.join(config_dir, "plugins", "DeDRM.zip") not in sys.path:
|
if os.path.join(config_dir, "plugins", "DeDRM.zip") not in sys.path:
|
||||||
sys.path.insert(0, os.path.join(config_dir, "plugins", "DeDRM.zip"))
|
sys.path.insert(0, os.path.join(config_dir, "plugins", "DeDRM.zip"))
|
||||||
|
|
||||||
# Explicitly set the package identifier so we are allowed to import stuff ...
|
if "calibre" in sys.modules:
|
||||||
#__package__ = "DeDRM_plugin"
|
# Explicitly set the package identifier so we are allowed to import stuff ...
|
||||||
|
__package__ = "calibre_plugins.dedrm"
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE_END@@
|
#@@CALIBRE_COMPAT_CODE_END@@
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from __future__ import print_function
|
||||||
|
|
||||||
# __init__.py for DeDRM_plugin
|
# __init__.py for DeDRM_plugin
|
||||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||||
# Copyright © 2021 NoDRM
|
# Copyright © 2021-2023 NoDRM
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
@ -82,6 +82,7 @@ __docformat__ = 'restructuredtext en'
|
||||||
# 10.0.0 - First forked version by NoDRM. See CHANGELOG.md for details.
|
# 10.0.0 - First forked version by NoDRM. See CHANGELOG.md for details.
|
||||||
# 10.0.1 - Fixes a bug in the watermark code.
|
# 10.0.1 - Fixes a bug in the watermark code.
|
||||||
# 10.0.2 - Fix Kindle for Mac & update Adobe key retrieval
|
# 10.0.2 - Fix Kindle for Mac & update Adobe key retrieval
|
||||||
|
# For changes made in 10.0.3 and above, see the CHANGELOG.md file
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt DRMed ebooks.
|
Decrypt DRMed ebooks.
|
||||||
|
|
@ -95,6 +96,9 @@ import traceback
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
try:
|
||||||
|
from . import __version
|
||||||
|
except:
|
||||||
import __version
|
import __version
|
||||||
except:
|
except:
|
||||||
print("#############################")
|
print("#############################")
|
||||||
|
|
@ -133,8 +137,10 @@ try:
|
||||||
except:
|
except:
|
||||||
config_dir = ""
|
config_dir = ""
|
||||||
|
|
||||||
|
try:
|
||||||
import utilities
|
from . import utilities
|
||||||
|
except:
|
||||||
|
import utilities
|
||||||
|
|
||||||
|
|
||||||
PLUGIN_NAME = __version.PLUGIN_NAME
|
PLUGIN_NAME = __version.PLUGIN_NAME
|
||||||
|
|
@ -210,12 +216,16 @@ class DeDRM(FileTypePlugin):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def postProcessEPUB(self, path_to_ebook):
|
def postProcessEPUB(self, path_to_ebook, path_to_original_ebook = None):
|
||||||
# This is called after the DRM is removed (or if no DRM was present)
|
# This is called after the DRM is removed (or if no DRM was present)
|
||||||
# It does stuff like de-obfuscating fonts (by calling checkFonts)
|
# It does stuff like de-obfuscating fonts (by calling checkFonts)
|
||||||
# or removing watermarks.
|
# or removing watermarks.
|
||||||
|
|
||||||
postProcessStart = time.time()
|
postProcessStart = time.time()
|
||||||
|
postProcessingNeeded = False
|
||||||
|
|
||||||
|
# Save a backup of the EPUB path after DRM removal but before any postprocessing is done.
|
||||||
|
pre_postprocessing_EPUB_path = path_to_ebook
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import prefs
|
import prefs
|
||||||
|
|
@ -242,6 +252,15 @@ class DeDRM(FileTypePlugin):
|
||||||
postProcessEnd = time.time()
|
postProcessEnd = time.time()
|
||||||
print("{0} v{1}: Post-processing took {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, postProcessEnd-postProcessStart))
|
print("{0} v{1}: Post-processing took {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, postProcessEnd-postProcessStart))
|
||||||
|
|
||||||
|
|
||||||
|
# If the EPUB is DRM-free (path_to_original_ebook will only be set in this case),
|
||||||
|
# and the post-processing hasn't changed anything in the EPUB,
|
||||||
|
# return the raw original file from path_to_original_ebook from before the
|
||||||
|
# zipfix code was executed.
|
||||||
|
if ((path_to_ebook == pre_postprocessing_EPUB_path) and path_to_original_ebook is not None):
|
||||||
|
print("{0} v{1}: Post-processing didn't do anything on DRM-free EPUB, returning original file".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
|
return path_to_original_ebook
|
||||||
|
|
||||||
return path_to_ebook
|
return path_to_ebook
|
||||||
|
|
||||||
except:
|
except:
|
||||||
|
|
@ -293,9 +312,9 @@ class DeDRM(FileTypePlugin):
|
||||||
# import the LCP handler
|
# import the LCP handler
|
||||||
import lcpdedrm
|
import lcpdedrm
|
||||||
|
|
||||||
if (lcpdedrm.isLCPbook(path_to_ebook)):
|
if (lcpdedrm.isLCPbook(inf.name)):
|
||||||
try:
|
try:
|
||||||
retval = lcpdedrm.decryptLCPbook(path_to_ebook, dedrmprefs['lcp_passphrases'], self)
|
retval = lcpdedrm.decryptLCPbook(inf.name, dedrmprefs['lcp_passphrases'], self)
|
||||||
except:
|
except:
|
||||||
print("Looks like that didn't work:")
|
print("Looks like that didn't work:")
|
||||||
raise
|
raise
|
||||||
|
|
@ -622,7 +641,7 @@ class DeDRM(FileTypePlugin):
|
||||||
|
|
||||||
# Not a Barnes & Noble nor an Adobe Adept
|
# Not a Barnes & Noble nor an Adobe Adept
|
||||||
# Probably a DRM-free EPUB, but we should still check for fonts.
|
# Probably a DRM-free EPUB, but we should still check for fonts.
|
||||||
return self.postProcessEPUB(inf.name)
|
return self.postProcessEPUB(inf.name, path_to_ebook)
|
||||||
|
|
||||||
|
|
||||||
def PDFIneptDecrypt(self, path_to_ebook):
|
def PDFIneptDecrypt(self, path_to_ebook):
|
||||||
|
|
@ -914,6 +933,9 @@ class DeDRM(FileTypePlugin):
|
||||||
# perhaps we need to get a new default Kindle for Mac/PC key
|
# perhaps we need to get a new default Kindle for Mac/PC key
|
||||||
defaultkeys = []
|
defaultkeys = []
|
||||||
print("{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0]))
|
print("{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0]))
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
print("{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print("{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,21 @@
|
||||||
# (CLI interface without Calibre)
|
# (CLI interface without Calibre)
|
||||||
# Copyright © 2021 NoDRM
|
# Copyright © 2021 NoDRM
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
NOTE: This code is not functional (yet). I started working on it a while ago
|
||||||
|
to make a standalone version of the plugins that could work without Calibre,
|
||||||
|
too, but for now there's only a rough code structure and no working code yet.
|
||||||
|
|
||||||
|
Currently, to use these plugins, you will need to use Calibre. Hopwfully that'll
|
||||||
|
change in the future.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
# For revision history see __init__.py
|
# For revision history see CHANGELOG.md
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Run DeDRM plugin without Calibre.
|
Run DeDRM plugin without Calibre.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
PLUGIN_NAME = "DeDRM"
|
PLUGIN_NAME = "DeDRM"
|
||||||
__version__ = '10.0.3'
|
__version__ = '10.0.9'
|
||||||
|
|
||||||
PLUGIN_VERSION_TUPLE = tuple([int(x) for x in __version__.split(".")])
|
PLUGIN_VERSION_TUPLE = tuple([int(x) for x in __version__.split(".")])
|
||||||
PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,11 @@ __version__ = '7.4'
|
||||||
import sys, os, struct, getopt
|
import sys, os, struct, getopt
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
|
||||||
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
from .utilities import SafeUnbuffered
|
||||||
from argv_utils import unicode_argv
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
||||||
# pbkdf2.py This code may be freely used and modified for any purpose.
|
# pbkdf2.py This code may be freely used and modified for any purpose.
|
||||||
|
|
||||||
|
import sys
|
||||||
import hmac
|
import hmac
|
||||||
from struct import pack
|
from struct import pack
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
@ -25,6 +26,9 @@ class Pukall_Cipher(object):
|
||||||
raise Exception("PC1: Bad key length")
|
raise Exception("PC1: Bad key length")
|
||||||
wkey = []
|
wkey = []
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||||||
|
else:
|
||||||
wkey.append(key[i*2]<<8 | key[i*2+1])
|
wkey.append(key[i*2]<<8 | key[i*2+1])
|
||||||
dst = bytearray(len(src))
|
dst = bytearray(len(src))
|
||||||
for i in range(len(src)):
|
for i in range(len(src)):
|
||||||
|
|
@ -37,7 +41,12 @@ class Pukall_Cipher(object):
|
||||||
sum2 = (sum2+sum1)&0xFFFF
|
sum2 = (sum2+sum1)&0xFFFF
|
||||||
temp1 = (temp1*20021+1)&0xFFFF
|
temp1 = (temp1*20021+1)&0xFFFF
|
||||||
byteXorVal ^= temp1 ^ sum2
|
byteXorVal ^= temp1 ^ sum2
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
curByte = ord(src[i])
|
||||||
|
else:
|
||||||
curByte = src[i]
|
curByte = src[i]
|
||||||
|
|
||||||
if not decryption:
|
if not decryption:
|
||||||
keyXorVal = curByte * 257;
|
keyXorVal = curByte * 257;
|
||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||||
|
|
@ -45,7 +54,12 @@ class Pukall_Cipher(object):
|
||||||
keyXorVal = curByte * 257;
|
keyXorVal = curByte * 257;
|
||||||
for j in range(8):
|
for j in range(8):
|
||||||
wkey[j] ^= keyXorVal;
|
wkey[j] ^= keyXorVal;
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
dst[i] = chr(curByte)
|
||||||
|
else:
|
||||||
dst[i] = curByte
|
dst[i] = curByte
|
||||||
|
|
||||||
return bytes(dst)
|
return bytes(dst)
|
||||||
|
|
||||||
class Topaz_Cipher(object):
|
class Topaz_Cipher(object):
|
||||||
|
|
@ -103,7 +117,7 @@ class KeyIVGen(object):
|
||||||
def xorbytes( a, b ):
|
def xorbytes( a, b ):
|
||||||
if len(a) != len(b):
|
if len(a) != len(b):
|
||||||
raise Exception("xorbytes(): lengths differ")
|
raise Exception("xorbytes(): lengths differ")
|
||||||
return bytes([x ^ y for x, y in zip(a, b)])
|
return bytes(bytearray([x ^ y for x, y in zip(a, b)]))
|
||||||
|
|
||||||
def prf( h, data ):
|
def prf( h, data ):
|
||||||
hm = h.copy()
|
hm = h.copy()
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,9 @@ def get_serials2(path=STORAGE2):
|
||||||
for y in tokens:
|
for y in tokens:
|
||||||
serials.append(y)
|
serials.append(y)
|
||||||
serials.append(x+y)
|
serials.append(x+y)
|
||||||
|
|
||||||
|
connection.close()
|
||||||
|
|
||||||
return serials
|
return serials
|
||||||
|
|
||||||
def get_serials(path=STORAGE):
|
def get_serials(path=STORAGE):
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ from calibre.constants import iswindows, isosx
|
||||||
|
|
||||||
from __init__ import PLUGIN_NAME, PLUGIN_VERSION
|
from __init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||||
from __version import RESOURCE_NAME as help_file_name
|
from __version import RESOURCE_NAME as help_file_name
|
||||||
from utilities import uStrCmp
|
from .utilities import uStrCmp
|
||||||
|
|
||||||
import prefs
|
import prefs
|
||||||
import androidkindlekey
|
import androidkindlekey
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@
|
||||||
# For use with Topaz Scripts Version 2.6
|
# For use with Topaz Scripts Version 2.6
|
||||||
# Python 3, September 2020
|
# Python 3, September 2020
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
|
||||||
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
from .utilities import SafeUnbuffered
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import csv
|
import csv
|
||||||
|
|
|
||||||
|
|
@ -49,16 +49,18 @@
|
||||||
|
|
||||||
__version__ = '2.0'
|
__version__ = '2.0'
|
||||||
|
|
||||||
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
import sys, struct, os, traceback
|
import sys, struct, os, traceback
|
||||||
import zlib
|
import zlib
|
||||||
import zipfile
|
import zipfile
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
from argv_utils import unicode_argv
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||||
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
from .utilities import SafeUnbuffered
|
||||||
|
|
||||||
|
|
||||||
_FILENAME_LEN_OFFSET = 26
|
_FILENAME_LEN_OFFSET = 26
|
||||||
|
|
|
||||||
|
|
@ -79,12 +79,13 @@ except ImportError:
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
from .utilities import SafeUnbuffered
|
||||||
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
iswindows = sys.platform.startswith('win')
|
iswindows = sys.platform.startswith('win')
|
||||||
isosx = sys.platform.startswith('darwin')
|
isosx = sys.platform.startswith('darwin')
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
|
||||||
|
|
||||||
import cgi
|
import cgi
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -141,14 +142,20 @@ def sanitizeFileName(name):
|
||||||
|
|
||||||
def fixKey(key):
|
def fixKey(key):
|
||||||
def fixByte(b):
|
def fixByte(b):
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
b = ord(b)
|
||||||
|
|
||||||
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
|
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
|
||||||
return bytes([fixByte(a) for a in key])
|
return bytes(bytearray([fixByte(a) for a in key]))
|
||||||
|
|
||||||
def deXOR(text, sp, table):
|
def deXOR(text, sp, table):
|
||||||
r=''
|
r=b''
|
||||||
j = sp
|
j = sp
|
||||||
for i in range(len(text)):
|
for i in range(len(text)):
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
r += chr(ord(table[j]) ^ ord(text[i]))
|
r += chr(ord(table[j]) ^ ord(text[i]))
|
||||||
|
else:
|
||||||
|
r += bytes(bytearray([table[j] ^ text[i]]))
|
||||||
j = j + 1
|
j = j + 1
|
||||||
if j == len(table):
|
if j == len(table):
|
||||||
j = 0
|
j = 0
|
||||||
|
|
@ -248,7 +255,7 @@ class EreaderProcessor(object):
|
||||||
encrypted_key = r[172:172+8]
|
encrypted_key = r[172:172+8]
|
||||||
encrypted_key_sha = r[56:56+20]
|
encrypted_key_sha = r[56:56+20]
|
||||||
self.content_key = des.decrypt(encrypted_key)
|
self.content_key = des.decrypt(encrypted_key)
|
||||||
if sha1(self.content_key).digest() != encrypted_key_sha:
|
if hashlib.sha1(self.content_key).digest() != encrypted_key_sha:
|
||||||
raise ValueError('Incorrect Name and/or Credit Card')
|
raise ValueError('Incorrect Name and/or Credit Card')
|
||||||
|
|
||||||
def getNumImages(self):
|
def getNumImages(self):
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
# Python 3 for calibre 5.0
|
# Python 3 for calibre 5.0
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
from .utilities import SafeUnbuffered
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import csv
|
import csv
|
||||||
|
|
|
||||||
|
|
@ -45,14 +45,16 @@ import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from Cryptodome.Cipher import AES
|
from Cryptodome.Cipher import AES
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
from .utilities import SafeUnbuffered
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,16 @@ import hashlib
|
||||||
import getopt
|
import getopt
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
from .utilities import SafeUnbuffered
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
except:
|
except:
|
||||||
iswindows = sys.platform.startswith('win')
|
iswindows = sys.platform.startswith('win')
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,10 @@ def unpad(data, padding=16):
|
||||||
|
|
||||||
return data[:-pad_len]
|
return data[:-pad_len]
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
from .utilities import SafeUnbuffered
|
||||||
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
|
|
||||||
class ADEPTError(Exception):
|
class ADEPTError(Exception):
|
||||||
|
|
|
||||||
|
|
@ -92,13 +92,14 @@ def unpad(data, padding=16):
|
||||||
|
|
||||||
return data[:-pad_len]
|
return data[:-pad_len]
|
||||||
|
|
||||||
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
from .utilities import SafeUnbuffered
|
||||||
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
iswindows = sys.platform.startswith('win')
|
iswindows = sys.platform.startswith('win')
|
||||||
isosx = sys.platform.startswith('darwin')
|
isosx = sys.platform.startswith('darwin')
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
|
||||||
|
|
||||||
class ADEPTError(Exception):
|
class ADEPTError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
@ -833,7 +834,7 @@ def num_value(x):
|
||||||
x = resolve1(x)
|
x = resolve1(x)
|
||||||
if not (isinstance(x, int) or isinstance(x, Decimal)):
|
if not (isinstance(x, int) or isinstance(x, Decimal)):
|
||||||
if STRICT:
|
if STRICT:
|
||||||
raise PDFTypeError('Int or Float required: %r' % x)
|
raise PDFTypeError('Int or Decimal required: %r' % x)
|
||||||
return 0
|
return 0
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
@ -1366,14 +1367,14 @@ class PDFDocument(object):
|
||||||
|
|
||||||
def process_with_aes(self, key, encrypt, data, repetitions = 1, iv = None):
|
def process_with_aes(self, key, encrypt, data, repetitions = 1, iv = None):
|
||||||
if iv is None:
|
if iv is None:
|
||||||
keylen = len(key)
|
iv = bytes(bytearray(16))
|
||||||
iv = bytes([0x00]*keylen)
|
|
||||||
|
aes = AES.new(key, AES.MODE_CBC, iv)
|
||||||
|
|
||||||
if not encrypt:
|
if not encrypt:
|
||||||
plaintext = AES.new(key,AES.MODE_CBC,iv, True).decrypt(data)
|
plaintext = aes.decrypt(data)
|
||||||
return plaintext
|
return plaintext
|
||||||
else:
|
else:
|
||||||
aes = AES.new(key, AES.MODE_CBC, iv, False)
|
|
||||||
new_data = bytes(data * repetitions)
|
new_data = bytes(data * repetitions)
|
||||||
crypt = aes.encrypt(new_data)
|
crypt = aes.encrypt(new_data)
|
||||||
return crypt
|
return crypt
|
||||||
|
|
@ -1394,10 +1395,18 @@ class PDFDocument(object):
|
||||||
raise Exception("K1 < 32 ...")
|
raise Exception("K1 < 32 ...")
|
||||||
#def process_with_aes(self, key: bytes, encrypt: bool, data: bytes, repetitions: int = 1, iv: bytes = None):
|
#def process_with_aes(self, key: bytes, encrypt: bool, data: bytes, repetitions: int = 1, iv: bytes = None):
|
||||||
E = self.process_with_aes(K[:16], True, K1, 64, K[16:32])
|
E = self.process_with_aes(K[:16], True, K1, 64, K[16:32])
|
||||||
K = (hashlib.sha256, hashlib.sha384, hashlib.sha512)[sum(E) % 3](E).digest()
|
E = bytearray(E)
|
||||||
|
|
||||||
|
E_mod_3 = 0
|
||||||
|
for i in range(16):
|
||||||
|
E_mod_3 += E[i]
|
||||||
|
|
||||||
|
E_mod_3 %= 3
|
||||||
|
|
||||||
|
K = (hashlib.sha256, hashlib.sha384, hashlib.sha512)[E_mod_3](E).digest()
|
||||||
|
|
||||||
if round_number >= 64:
|
if round_number >= 64:
|
||||||
ch = int.from_bytes(E[-1:], "big", signed=False)
|
ch = E[-1:][0] # get last byte
|
||||||
if ch <= round_number - 32:
|
if ch <= round_number - 32:
|
||||||
done = True
|
done = True
|
||||||
|
|
||||||
|
|
@ -1478,14 +1487,23 @@ class PDFDocument(object):
|
||||||
EncMetadata = b'True'
|
EncMetadata = b'True'
|
||||||
if (EncMetadata == ('False' or 'false') or V < 4) and R >= 4:
|
if (EncMetadata == ('False' or 'false') or V < 4) and R >= 4:
|
||||||
hash.update(codecs.decode(b'ffffffff','hex'))
|
hash.update(codecs.decode(b'ffffffff','hex'))
|
||||||
|
|
||||||
|
# Finish hash:
|
||||||
|
hash = hash.digest()
|
||||||
|
|
||||||
if R >= 3:
|
if R >= 3:
|
||||||
# 8
|
# 8
|
||||||
for _ in range(50):
|
for _ in range(50):
|
||||||
hash = hashlib.md5(hash.digest()[:length//8])
|
hash = hashlib.md5(hash[:length//8]).digest()
|
||||||
key = hash.digest()[:length//8]
|
if R == 2:
|
||||||
|
# R=2 only uses first five bytes.
|
||||||
|
key = hash[:5]
|
||||||
|
else:
|
||||||
|
key = hash[:length//8]
|
||||||
|
|
||||||
if R == 2:
|
if R == 2:
|
||||||
# Algorithm 3.4
|
# Algorithm 3.4
|
||||||
u1 = ARC4.new(key).decrypt(password)
|
u1 = ARC4.new(key).decrypt(self.PASSWORD_PADDING)
|
||||||
elif R >= 3:
|
elif R >= 3:
|
||||||
# Algorithm 3.5
|
# Algorithm 3.5
|
||||||
hash = hashlib.md5(self.PASSWORD_PADDING) # 2
|
hash = hashlib.md5(self.PASSWORD_PADDING) # 2
|
||||||
|
|
@ -1498,6 +1516,7 @@ class PDFDocument(object):
|
||||||
k = b''.join(bytes([c ^ i]) for c in key )
|
k = b''.join(bytes([c ^ i]) for c in key )
|
||||||
x = ARC4.new(k).decrypt(x)
|
x = ARC4.new(k).decrypt(x)
|
||||||
u1 = x+x # 32bytes total
|
u1 = x+x # 32bytes total
|
||||||
|
|
||||||
if R == 2:
|
if R == 2:
|
||||||
is_authenticated = (u1 == U)
|
is_authenticated = (u1 == U)
|
||||||
else:
|
else:
|
||||||
|
|
@ -2023,7 +2042,7 @@ class PDFParser(PSStackParser):
|
||||||
except PDFNoValidXRef:
|
except PDFNoValidXRef:
|
||||||
# fallback
|
# fallback
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
pat = re.compile(b'^(\\d+)\\s+(\\d+)\\s+obj\\b')
|
pat = re.compile(br'^(\\d+)\\s+(\\d+)\\s+obj\\b')
|
||||||
offsets = {}
|
offsets = {}
|
||||||
xref = PDFXRef()
|
xref = PDFXRef()
|
||||||
while 1:
|
while 1:
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,19 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ion.py
|
"""ion.py: Decrypt Kindle KFX files.
|
||||||
# Copyright © 2013-2020 Apprentice Harper et al.
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
Revision history:
|
||||||
__version__ = '3.0'
|
Pascal implementation by lulzkabulz.
|
||||||
|
BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
||||||
|
1.0 - Python translation by apprenticenaomi.
|
||||||
|
1.1 - DeDRM integration by anon.
|
||||||
|
1.2 - Added pylzma import fallback
|
||||||
|
1.3 - Fixed lzma support for calibre 4.6+
|
||||||
|
2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
|
||||||
|
3.0 - Added Python 3 compatibility for calibre 5.0
|
||||||
|
|
||||||
# Revision history:
|
Copyright © 2013-2020 Apprentice Harper et al.
|
||||||
# Pascal implementation by lulzkabulz.
|
|
||||||
# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
|
|
||||||
# 1.0 - Python translation by apprenticenaomi.
|
|
||||||
# 1.1 - DeDRM integration by anon.
|
|
||||||
# 1.2 - Added pylzma import fallback
|
|
||||||
# 1.3 - Fixed lzma support for calibre 4.6+
|
|
||||||
# 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
|
|
||||||
# 3.0 - Added Python 3 compatibility for calibre 5.0
|
|
||||||
|
|
||||||
"""
|
|
||||||
Decrypt Kindle KFX files.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
|
@ -30,6 +25,12 @@ import struct
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__version__ = '3.0'
|
||||||
|
|
||||||
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from Cryptodome.Cipher import AES
|
from Cryptodome.Cipher import AES
|
||||||
from Cryptodome.Util.py3compat import bchr
|
from Cryptodome.Util.py3compat import bchr
|
||||||
|
|
@ -57,7 +58,7 @@ except ImportError:
|
||||||
# Windows-friendly choice: pylzma wheels
|
# Windows-friendly choice: pylzma wheels
|
||||||
import pylzma as lzma
|
import pylzma as lzma
|
||||||
|
|
||||||
from kfxtables import *
|
from .kfxtables import *
|
||||||
|
|
||||||
TID_NULL = 0
|
TID_NULL = 0
|
||||||
TID_BOOLEAN = 1
|
TID_BOOLEAN = 1
|
||||||
|
|
@ -1346,7 +1347,7 @@ class DrmIonVoucher(object):
|
||||||
process_V4648(shared), process_V5683(shared)]
|
process_V4648(shared), process_V5683(shared)]
|
||||||
|
|
||||||
decrypted=False
|
decrypted=False
|
||||||
ex=None
|
lastexception = None # type: Exception | None
|
||||||
for sharedsecret in sharedsecrets:
|
for sharedsecret in sharedsecrets:
|
||||||
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
||||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||||
|
|
@ -1363,9 +1364,10 @@ class DrmIonVoucher(object):
|
||||||
print("Decryption succeeded")
|
print("Decryption succeeded")
|
||||||
break
|
break
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
lastexception = ex
|
||||||
print("Decryption failed, trying next fallback ")
|
print("Decryption failed, trying next fallback ")
|
||||||
if not decrypted:
|
if not decrypted:
|
||||||
raise ex
|
raise lastexception
|
||||||
|
|
||||||
self.drmkey.stepin()
|
self.drmkey.stepin()
|
||||||
while self.drmkey.hasnext():
|
while self.drmkey.hasnext():
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,9 @@ import kgenpids
|
||||||
import androidkindlekey
|
import androidkindlekey
|
||||||
import kfxdedrm
|
import kfxdedrm
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
from .utilities import SafeUnbuffered
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
|
|
||||||
# cleanup unicode filenames
|
# cleanup unicode filenames
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class KFXZipBook:
|
||||||
# Belt and braces. PIDs should be unicode strings, but just in case...
|
# Belt and braces. PIDs should be unicode strings, but just in case...
|
||||||
if isinstance(pid, bytes):
|
if isinstance(pid, bytes):
|
||||||
pid = pid.decode('ascii')
|
pid = pid.decode('ascii')
|
||||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
|
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,0), (32,40), (40,0), (40,40)]:
|
||||||
if len(pid) == dsn_len + secret_len:
|
if len(pid) == dsn_len + secret_len:
|
||||||
break # split pid into DSN and account secret
|
break # split pid into DSN and account secret
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,17 @@ def SHA1(message):
|
||||||
def encode(data, map):
|
def encode(data, map):
|
||||||
result = b''
|
result = b''
|
||||||
for char in data:
|
for char in data:
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
value = ord(char)
|
||||||
|
else:
|
||||||
value = char
|
value = char
|
||||||
|
|
||||||
Q = (value ^ 0x80) // len(map)
|
Q = (value ^ 0x80) // len(map)
|
||||||
R = value % len(map)
|
R = value % len(map)
|
||||||
result += bytes([map[Q]])
|
|
||||||
result += bytes([map[R]])
|
result += bytes(bytearray([map[Q]]))
|
||||||
|
result += bytes(bytearray([map[R]]))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Hash the bytes in data and then encode the digest with the characters in map
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||||||
|
|
@ -84,6 +90,9 @@ def decode(data,map):
|
||||||
def getTwoBitsFromBitField(bitField,offset):
|
def getTwoBitsFromBitField(bitField,offset):
|
||||||
byteNumber = offset // 4
|
byteNumber = offset // 4
|
||||||
bitPosition = 6 - 2*(offset % 4)
|
bitPosition = 6 - 2*(offset % 4)
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
return ord(bitField[byteNumber]) >> bitPosition & 3
|
||||||
|
else:
|
||||||
return bitField[byteNumber] >> bitPosition & 3
|
return bitField[byteNumber] >> bitPosition & 3
|
||||||
|
|
||||||
# Returns the six bits at offset from a bit field
|
# Returns the six bits at offset from a bit field
|
||||||
|
|
@ -97,7 +106,8 @@ def encodePID(hash):
|
||||||
global charMap3
|
global charMap3
|
||||||
PID = b''
|
PID = b''
|
||||||
for position in range (0,8):
|
for position in range (0,8):
|
||||||
PID += bytes([charMap3[getSixBitsFromBitField(hash,position)]])
|
PID += bytes(bytearray([charMap3[getSixBitsFromBitField(hash,position)]]))
|
||||||
|
|
||||||
return PID
|
return PID
|
||||||
|
|
||||||
# Encryption table used to generate the device PID
|
# Encryption table used to generate the device PID
|
||||||
|
|
@ -134,7 +144,7 @@ def generateDevicePID(table,dsn,nbRoll):
|
||||||
index = (index+1) %8
|
index = (index+1) %8
|
||||||
for counter in range (0,8):
|
for counter in range (0,8):
|
||||||
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
||||||
pidAscii += bytes([charMap4[index]])
|
pidAscii += bytes(bytearray([charMap4[index]]))
|
||||||
return pidAscii
|
return pidAscii
|
||||||
|
|
||||||
def crc32(s):
|
def crc32(s):
|
||||||
|
|
@ -150,7 +160,7 @@ def checksumPid(s):
|
||||||
for i in (0,1):
|
for i in (0,1):
|
||||||
b = crc & 0xff
|
b = crc & 0xff
|
||||||
pos = (b // l) ^ (b % l)
|
pos = (b // l) ^ (b % l)
|
||||||
res += bytes([charMap4[pos%l]])
|
res += bytes(bytearray([charMap4[pos%l]]))
|
||||||
crc >>= 8
|
crc >>= 8
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
@ -161,6 +171,9 @@ def pidFromSerial(s, l):
|
||||||
crc = crc32(s)
|
crc = crc32(s)
|
||||||
arr1 = [0]*l
|
arr1 = [0]*l
|
||||||
for i in range(len(s)):
|
for i in range(len(s)):
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
arr1[i%l] ^= ord(s[i])
|
||||||
|
else:
|
||||||
arr1[i%l] ^= s[i]
|
arr1[i%l] ^= s[i]
|
||||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
||||||
for i in range(l):
|
for i in range(l):
|
||||||
|
|
@ -168,7 +181,7 @@ def pidFromSerial(s, l):
|
||||||
pid = b""
|
pid = b""
|
||||||
for i in range(l):
|
for i in range(l):
|
||||||
b = arr1[i] & 0xff
|
b = arr1[i] & 0xff
|
||||||
pid += bytes([charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]])
|
pid += bytes(bytearray([charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]]))
|
||||||
return pid
|
return pid
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -177,6 +190,10 @@ def getKindlePids(rec209, token, serialnum):
|
||||||
if isinstance(serialnum,str):
|
if isinstance(serialnum,str):
|
||||||
serialnum = serialnum.encode('utf-8')
|
serialnum = serialnum.encode('utf-8')
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
if isinstance(serialnum,unicode):
|
||||||
|
serialnum = serialnum.encode('utf-8')
|
||||||
|
|
||||||
if rec209 is None:
|
if rec209 is None:
|
||||||
return [serialnum]
|
return [serialnum]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,11 @@ except NameError:
|
||||||
|
|
||||||
# Routines common to Mac and PC
|
# Routines common to Mac and PC
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
from .utilities import SafeUnbuffered
|
||||||
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from calibre.constants import iswindows, isosx
|
from calibre.constants import iswindows, isosx
|
||||||
|
|
@ -70,7 +74,7 @@ except:
|
||||||
iswindows = sys.platform.startswith('win')
|
iswindows = sys.platform.startswith('win')
|
||||||
isosx = sys.platform.startswith('darwin')
|
isosx = sys.platform.startswith('darwin')
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
@ -115,11 +119,17 @@ def primes(n):
|
||||||
def encode(data, map):
|
def encode(data, map):
|
||||||
result = b''
|
result = b''
|
||||||
for char in data:
|
for char in data:
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
value = ord(char)
|
||||||
|
else:
|
||||||
value = char
|
value = char
|
||||||
|
|
||||||
Q = (value ^ 0x80) // len(map)
|
Q = (value ^ 0x80) // len(map)
|
||||||
R = value % len(map)
|
R = value % len(map)
|
||||||
result += bytes([map[Q]])
|
|
||||||
result += bytes([map[R]])
|
result += bytes(bytearray([map[Q]]))
|
||||||
|
result += bytes(bytearray([map[R]]))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Hash the bytes in data and then encode the digest with the characters in map
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||||||
|
|
@ -232,6 +242,11 @@ if iswindows:
|
||||||
|
|
||||||
# replace any non-ASCII values with 0xfffd
|
# replace any non-ASCII values with 0xfffd
|
||||||
for i in range(0,len(buffer)):
|
for i in range(0,len(buffer)):
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
if buffer[i]>u"\u007f":
|
||||||
|
#print "swapping char "+str(i)+" ("+buffer[i]+")"
|
||||||
|
buffer[i] = u"\ufffd"
|
||||||
|
else:
|
||||||
if buffer[i]>"\u007f":
|
if buffer[i]>"\u007f":
|
||||||
#print "swapping char "+str(i)+" ("+buffer[i]+")"
|
#print "swapping char "+str(i)+" ("+buffer[i]+")"
|
||||||
buffer[i] = "\ufffd"
|
buffer[i] = "\ufffd"
|
||||||
|
|
|
||||||
|
|
@ -16,24 +16,25 @@
|
||||||
import sys
|
import sys
|
||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
from .utilities import SafeUnbuffered
|
||||||
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
letters = b'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||||
|
|
||||||
def crc32(s):
|
def crc32(s):
|
||||||
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||||
|
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
crc = crc32(s.encode('ascii'))
|
crc = crc32(s)
|
||||||
crc = crc ^ (crc >> 16)
|
crc = crc ^ (crc >> 16)
|
||||||
res = s
|
res = s
|
||||||
l = len(letters)
|
l = len(letters)
|
||||||
for i in (0,1):
|
for i in (0,1):
|
||||||
b = crc & 0xff
|
b = crc & 0xff
|
||||||
pos = (b // l) ^ (b % l)
|
pos = (b // l) ^ (b % l)
|
||||||
res += letters[pos%l]
|
res += bytes(bytearray([letters[pos%l]]))
|
||||||
crc >>= 8
|
crc >>= 8
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
@ -43,16 +44,19 @@ def pidFromSerial(s, l):
|
||||||
|
|
||||||
arr1 = [0]*l
|
arr1 = [0]*l
|
||||||
for i in range(len(s)):
|
for i in range(len(s)):
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
arr1[i%l] ^= ord(s[i])
|
||||||
|
else:
|
||||||
arr1[i%l] ^= s[i]
|
arr1[i%l] ^= s[i]
|
||||||
|
|
||||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
||||||
for i in range(l):
|
for i in range(l):
|
||||||
arr1[i] ^= crc_bytes[i&3]
|
arr1[i] ^= crc_bytes[i&3]
|
||||||
|
|
||||||
pid = ''
|
pid = b""
|
||||||
for i in range(l):
|
for i in range(l):
|
||||||
b = arr1[i] & 0xff
|
b = arr1[i] & 0xff
|
||||||
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
pid+=bytes(bytearray([letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]]))
|
||||||
|
|
||||||
return pid
|
return pid
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,14 @@ import sys
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import binascii
|
import binascii
|
||||||
from alfcrypto import Pukall_Cipher
|
|
||||||
|
|
||||||
from utilities import SafeUnbuffered
|
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
|
|
||||||
|
from .alfcrypto import Pukall_Cipher
|
||||||
|
from .utilities import SafeUnbuffered
|
||||||
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
|
|
@ -103,19 +106,26 @@ def PC1(key, src, decryption=True):
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# accepts unicode returns unicode
|
letters = b'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||||
|
|
||||||
|
def crc32(s):
|
||||||
|
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||||
|
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
|
||||||
crc = (~binascii.crc32(s.encode('utf-8'),-1))&0xFFFFFFFF
|
s = s.encode()
|
||||||
|
|
||||||
|
|
||||||
|
crc = crc32(s)
|
||||||
crc = crc ^ (crc >> 16)
|
crc = crc ^ (crc >> 16)
|
||||||
res = s
|
res = s
|
||||||
l = len(letters)
|
l = len(letters)
|
||||||
for i in (0,1):
|
for i in (0,1):
|
||||||
b = crc & 0xff
|
b = crc & 0xff
|
||||||
pos = (b // l) ^ (b % l)
|
pos = (b // l) ^ (b % l)
|
||||||
res += letters[pos%l]
|
res += bytes(bytearray([letters[pos%l]]))
|
||||||
crc >>= 8
|
crc >>= 8
|
||||||
return res
|
return res.decode()
|
||||||
|
|
||||||
# expects bytearray
|
# expects bytearray
|
||||||
def getSizeOfTrailingDataEntries(ptr, size, flags):
|
def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||||
|
|
@ -124,7 +134,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||||
if size <= 0:
|
if size <= 0:
|
||||||
return result
|
return result
|
||||||
while True:
|
while True:
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
v = ord(ptr[size-1])
|
||||||
|
else:
|
||||||
v = ptr[size-1]
|
v = ptr[size-1]
|
||||||
|
|
||||||
result |= (v & 0x7F) << bitpos
|
result |= (v & 0x7F) << bitpos
|
||||||
bitpos += 7
|
bitpos += 7
|
||||||
size -= 1
|
size -= 1
|
||||||
|
|
@ -140,6 +154,9 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||||
# if multibyte data is included in the encryped data, we'll
|
# if multibyte data is included in the encryped data, we'll
|
||||||
# have already cleared this flag.
|
# have already cleared this flag.
|
||||||
if flags & 1:
|
if flags & 1:
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
||||||
|
else:
|
||||||
num += (ptr[size - num - 1] & 0x3) + 1
|
num += (ptr[size - num - 1] & 0x3) + 1
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
@ -299,6 +316,9 @@ class MobiBook:
|
||||||
for pid in pidlist:
|
for pid in pidlist:
|
||||||
bigpid = pid.encode('utf-8').ljust(16,b'\0')
|
bigpid = pid.encode('utf-8').ljust(16,b'\0')
|
||||||
temp_key = PC1(keyvec1, bigpid, False)
|
temp_key = PC1(keyvec1, bigpid, False)
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||||
|
else:
|
||||||
temp_key_sum = sum(temp_key) & 0xff
|
temp_key_sum = sum(temp_key) & 0xff
|
||||||
found_key = None
|
found_key = None
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
|
|
@ -315,7 +335,11 @@ class MobiBook:
|
||||||
# Then try the default encoding that doesn't require a PID
|
# Then try the default encoding that doesn't require a PID
|
||||||
pid = '00000000'
|
pid = '00000000'
|
||||||
temp_key = keyvec1
|
temp_key = keyvec1
|
||||||
|
if sys.version_info[0] == 2:
|
||||||
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||||
|
else:
|
||||||
temp_key_sum = sum(temp_key) & 0xff
|
temp_key_sum = sum(temp_key) & 0xff
|
||||||
|
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||||||
if cksum == temp_key_sum:
|
if cksum == temp_key_sum:
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,18 @@ from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
# Copyright © 2021 NoDRM
|
# Copyright © 2021 NoDRM
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
NOTE: This code is not functional (yet). I started working on it a while ago
|
||||||
|
to make a standalone version of the plugins that could work without Calibre,
|
||||||
|
too, but for now there's only a rough code structure and no working code yet.
|
||||||
|
|
||||||
|
Currently, to use these plugins, you will need to use Calibre. Hopwfully that'll
|
||||||
|
change in the future.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
OPT_SHORT_TO_LONG = [
|
OPT_SHORT_TO_LONG = [
|
||||||
["c", "config"],
|
["c", "config"],
|
||||||
["e", "extract"],
|
["e", "extract"],
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,17 @@ from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
# Taken from Calibre code - Copyright © 2008, Kovid Goyal kovid@kovidgoyal.net, GPLv3
|
# Taken from Calibre code - Copyright © 2008, Kovid Goyal kovid@kovidgoyal.net, GPLv3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
NOTE: This code is not functional (yet). I started working on it a while ago
|
||||||
|
to make a standalone version of the plugins that could work without Calibre,
|
||||||
|
too, but for now there's only a rough code structure and no working code yet.
|
||||||
|
|
||||||
|
Currently, to use these plugins, you will need to use Calibre. Hopwfully that'll
|
||||||
|
change in the future.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
import sys, os, codecs, json
|
import sys, os, codecs, json
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,17 @@ from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
# Copyright © 2021 NoDRM
|
# Copyright © 2021 NoDRM
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
NOTE: This code is not functional (yet). I started working on it a while ago
|
||||||
|
to make a standalone version of the plugins that could work without Calibre,
|
||||||
|
too, but for now there's only a rough code structure and no working code yet.
|
||||||
|
|
||||||
|
Currently, to use these plugins, you will need to use Calibre. Hopwfully that'll
|
||||||
|
change in the future.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,17 @@ from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
# Copyright © 2021 NoDRM
|
# Copyright © 2021 NoDRM
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
NOTE: This code is not functional (yet). I started working on it a while ago
|
||||||
|
to make a standalone version of the plugins that could work without Calibre,
|
||||||
|
too, but for now there's only a rough code structure and no working code yet.
|
||||||
|
|
||||||
|
Currently, to use these plugins, you will need to use Calibre. Hopwfully that'll
|
||||||
|
change in the future.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@ import traceback
|
||||||
from struct import pack
|
from struct import pack
|
||||||
from struct import unpack
|
from struct import unpack
|
||||||
|
|
||||||
from alfcrypto import Topaz_Cipher
|
from .alfcrypto import Topaz_Cipher
|
||||||
from utilities import SafeUnbuffered
|
from .utilities import SafeUnbuffered
|
||||||
|
|
||||||
from argv_utils import unicode_argv
|
from .argv_utils import unicode_argv
|
||||||
|
|
||||||
|
|
||||||
#global switch
|
#global switch
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||||
print_function)
|
print_function)
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '10.0.3'
|
__version__ = '10.0.9'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
|
|
@ -20,7 +20,7 @@ except NameError:
|
||||||
PLUGIN_NAME = 'Obok DeDRM'
|
PLUGIN_NAME = 'Obok DeDRM'
|
||||||
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
||||||
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
||||||
PLUGIN_VERSION_TUPLE = (10, 0, 3)
|
PLUGIN_VERSION_TUPLE = (10, 0, 9)
|
||||||
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
||||||
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
||||||
PLUGIN_AUTHORS = 'Anon'
|
PLUGIN_AUTHORS = 'Anon'
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,11 @@ class InterfacePluginAction(InterfaceAction):
|
||||||
result['success'] = False
|
result['success'] = False
|
||||||
result['fileobj'] = None
|
result['fileobj'] = None
|
||||||
|
|
||||||
|
try:
|
||||||
zin = zipfile.ZipFile(book.filename, 'r')
|
zin = zipfile.ZipFile(book.filename, 'r')
|
||||||
|
except FileNotFoundError:
|
||||||
|
print (_('{0} - File "{1}" not found. Make sure the eBook has been properly downloaded in the Kobo app.').format(PLUGIN_NAME, book.filename))
|
||||||
|
return result
|
||||||
#print ('Kobo library filename: {0}'.format(book.filename))
|
#print ('Kobo library filename: {0}'.format(book.filename))
|
||||||
for userkey in self.userkeys:
|
for userkey in self.userkeys:
|
||||||
print (_('Trying key: '), codecs.encode(userkey, 'hex'))
|
print (_('Trying key: '), codecs.encode(userkey, 'hex'))
|
||||||
|
|
|
||||||
|
|
@ -168,8 +168,8 @@
|
||||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = '10.0.1'
|
__version__ = '10.0.9'
|
||||||
__about__ = "Obok v{0}\nCopyright © 2012-2022 Physisticated et al.".format(__version__)
|
__about__ = "Obok v{0}\nCopyright © 2012-2023 Physisticated et al.".format(__version__)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
@ -449,9 +449,15 @@ class KoboLibrary(object):
|
||||||
for m in matches:
|
for m in matches:
|
||||||
# print "m:{0}".format(m[0])
|
# print "m:{0}".format(m[0])
|
||||||
macaddrs.append(m[0].upper())
|
macaddrs.append(m[0].upper())
|
||||||
|
elif sys.platform.startswith('linux'):
|
||||||
|
for interface in os.listdir('/sys/class/net'):
|
||||||
|
with open('/sys/class/net/' + interface + '/address', 'r') as f:
|
||||||
|
mac = f.read().strip().upper()
|
||||||
|
# some interfaces, like Tailscale's VPN interface, do not have a MAC address
|
||||||
|
if mac != '':
|
||||||
|
macaddrs.append(mac)
|
||||||
else:
|
else:
|
||||||
# probably linux
|
# final fallback
|
||||||
|
|
||||||
# let's try ip
|
# let's try ip
|
||||||
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
for line in os.popen('ip -br link'):
|
for line in os.popen('ip -br link'):
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h1>Obok DeDRM Plugin</h1>
|
<h1>Obok DeDRM Plugin</h1>
|
||||||
<h3>(version 10.0.2)</h3>
|
<h3>(version 10.0.9 / 10.1.0 RC1)</h3>
|
||||||
|
|
||||||
<h3>Installation:</h3>
|
<h3>Installation:</h3>
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,11 +3,13 @@ DeDRM tools for ebooks
|
||||||
|
|
||||||
This is a fork of Apprentice Harper's version of the DeDRM tools. Apprentice Harper said that the original version of the plugin [is no longer maintained](https://github.com/apprenticeharper/DeDRM_tools#no-longer-maintained), so I've taken over, merged a bunch of open PRs, and added a ton more features and bugfixes.
|
This is a fork of Apprentice Harper's version of the DeDRM tools. Apprentice Harper said that the original version of the plugin [is no longer maintained](https://github.com/apprenticeharper/DeDRM_tools#no-longer-maintained), so I've taken over, merged a bunch of open PRs, and added a ton more features and bugfixes.
|
||||||
|
|
||||||
The latest stable (released) version is v10.0.3 which [can be downloaded here](https://github.com/noDRM/DeDRM_tools/releases/tag/v10.0.3). The latest `master` build (will be automatically updated with every code change, may be unstable) [can be found here](https://github.com/noDRM/DeDRM_tools/releases/tag/autorelease).
|
The latest stable (released) version is v10.0.3 which [can be downloaded here](https://github.com/noDRM/DeDRM_tools/releases/tag/v10.0.3). The latest beta is v10.0.9, as a release candidate for v10.1.0. It [can be downloaded here](https://github.com/noDRM/DeDRM_tools/releases/tag/v10.0.9).
|
||||||
|
|
||||||
Take a look at [the CHANGELOG](https://github.com/noDRM/DeDRM_tools/blob/master/CHANGELOG.md) to see a list of changes since the last version by Apprentice Harper (v7.2.1). This plugin will start with version v10.0.0.
|
The latest alpha version is available [at this link](https://github.com/noDRM/DeDRM_tools_autorelease/releases). This version is completely untested and will contain the latest code changes in this repository. With each commit in this repository, a new automatic alpha version will be uploaded there. If you want the most up-to-date code to test things and are okay with the plugin occasionally breaking, you can download this version.
|
||||||
|
|
||||||
The v10.0.0 versions of this plugin should both work with Calibre 5.x (Python 3) as well as Calibre 4.x and lower (Python 2). If you encounter issues with this plugin in Calibre 4.x or lower, please open a bug report.
|
Take a look at [the CHANGELOG](https://github.com/noDRM/DeDRM_tools/blob/master/CHANGELOG.md) to see a list of changes since the last version by Apprentice Harper (v7.2.1).
|
||||||
|
|
||||||
|
My version of the plugin should both work with Calibre 5.x/6.x (Python 3) as well as Calibre 4.x and lower (Python 2). If you encounter issues with this plugin in Calibre 4.x or lower, please open a bug report.
|
||||||
|
|
||||||
# Original README from Apprentice Harper
|
# Original README from Apprentice Harper
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ Installation
|
||||||
------------
|
------------
|
||||||
Open calibre's Preferences dialog. Click on the "Plugins" button. Next, click on the button, "Load plugin from file". Navigate to the unzipped DeDRM_tools folder, find the file "obok_plugin.zip". Click to select the file and select "Open". Click "Yes" in the "Are you sure?" dialog box. Click the "OK" button in the "Success" dialog box.
|
Open calibre's Preferences dialog. Click on the "Plugins" button. Next, click on the button, "Load plugin from file". Navigate to the unzipped DeDRM_tools folder, find the file "obok_plugin.zip". Click to select the file and select "Open". Click "Yes" in the "Are you sure?" dialog box. Click the "OK" button in the "Success" dialog box.
|
||||||
|
|
||||||
|
Note: This plugin requires the "wmic" component on Windows. On Windows 10 and below this will be available by default, on Windows 11 it needs to be explicitly enabled. Make sure that on your Windows 11 machine, under Settings -> System -> Optional features -> Add an optional feature -> View features, "WMIC" is enabled / activated, otherwise this plugin may not work correctly.
|
||||||
|
|
||||||
|
|
||||||
Customization
|
Customization
|
||||||
-------------
|
-------------
|
||||||
|
|
@ -16,7 +18,6 @@ No customization is required, except choosing which menus will show the plugin.
|
||||||
|
|
||||||
Using the plugin
|
Using the plugin
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Select the plugin's menu or icon from whichever part of the calibre interface you have chosen to have it. Follow the instructions in the dialog that appears.
|
Select the plugin's menu or icon from whichever part of the calibre interface you have chosen to have it. Follow the instructions in the dialog that appears.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -29,5 +30,5 @@ If you find that the DeDRM plugin is not working for you (imported ebooks still
|
||||||
- Once calibre has re-started, import the problem ebook.
|
- Once calibre has re-started, import the problem ebook.
|
||||||
- Now close calibre.
|
- Now close calibre.
|
||||||
|
|
||||||
A log will appear that you can copy and paste into a comment at Apprentice Alf's blog, http://apprenticealf.wordpress.com/ or an issue at Apprentice Harper's repository, https://github.com/apprenticeharper/DeDRM_tools/issues . You should also give details of your computer, and how you obtained the ebook file.
|
A log will appear that you can copy and paste into a GitHub issue at noDRM's repository, https://github.com/noDRM/DeDRM_tools/issues . You should also give details of your computer, and how you obtained the ebook file.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue