mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-20 13:08:55 +00:00
Compare commits
16 Commits
0ed8bd8ed2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7379b45319 | ||
|
|
bde82fd7ab | ||
|
|
de3d91f5e5 | ||
|
|
c5ee327a60 | ||
|
|
501a1e6d31 | ||
|
|
815d86efe0 | ||
|
|
65646f4493 | ||
|
|
808dc7d29a | ||
|
|
2cd2792306 | ||
|
|
2e53d70e88 | ||
|
|
05fff5217b | ||
|
|
34c4c067e8 | ||
|
|
195ea69537 | ||
|
|
3373d93874 | ||
|
|
bf2471e65b | ||
|
|
5492dcdbf4 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
run: python3 make_release.py
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: plugin
|
||||
path: |
|
||||
|
||||
@@ -105,4 +105,9 @@ This is v10.0.9, a release candidate for v10.1.0. I don't expect there to be maj
|
||||
- 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).
|
||||
|
||||
|
||||
@@ -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>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>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -216,12 +216,16 @@ class DeDRM(FileTypePlugin):
|
||||
traceback.print_exc()
|
||||
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)
|
||||
# It does stuff like de-obfuscating fonts (by calling checkFonts)
|
||||
# or removing watermarks.
|
||||
|
||||
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:
|
||||
import prefs
|
||||
@@ -248,6 +252,15 @@ class DeDRM(FileTypePlugin):
|
||||
postProcessEnd = time.time()
|
||||
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
|
||||
|
||||
except:
|
||||
@@ -299,9 +312,9 @@ class DeDRM(FileTypePlugin):
|
||||
# import the LCP handler
|
||||
import lcpdedrm
|
||||
|
||||
if (lcpdedrm.isLCPbook(path_to_ebook)):
|
||||
if (lcpdedrm.isLCPbook(inf.name)):
|
||||
try:
|
||||
retval = lcpdedrm.decryptLCPbook(path_to_ebook, dedrmprefs['lcp_passphrases'], self)
|
||||
retval = lcpdedrm.decryptLCPbook(inf.name, dedrmprefs['lcp_passphrases'], self)
|
||||
except:
|
||||
print("Looks like that didn't work:")
|
||||
raise
|
||||
@@ -628,7 +641,7 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
# Not a Barnes & Noble nor an Adobe Adept
|
||||
# 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):
|
||||
|
||||
@@ -255,7 +255,7 @@ class EreaderProcessor(object):
|
||||
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:
|
||||
if hashlib.sha1(self.content_key).digest() != encrypted_key_sha:
|
||||
raise ValueError('Incorrect Name and/or Credit Card')
|
||||
|
||||
def getNumImages(self):
|
||||
|
||||
@@ -2042,7 +2042,7 @@ class PDFParser(PSStackParser):
|
||||
except PDFNoValidXRef:
|
||||
# fallback
|
||||
self.seek(0)
|
||||
pat = re.compile(rb'^(\\d+)\\s+(\\d+)\\s+obj\\b')
|
||||
pat = re.compile(br'^(\\d+)\\s+(\\d+)\\s+obj\\b')
|
||||
offsets = {}
|
||||
xref = PDFXRef()
|
||||
while 1:
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ion.py
|
||||
# Copyright © 2013-2020 Apprentice Harper et al.
|
||||
"""ion.py: Decrypt Kindle KFX files.
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '3.0'
|
||||
Revision history:
|
||||
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:
|
||||
# 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.
|
||||
Copyright © 2013-2020 Apprentice Harper et al.
|
||||
"""
|
||||
|
||||
import collections
|
||||
@@ -30,6 +25,9 @@ import struct
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '3.0'
|
||||
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
|
||||
@@ -1096,11 +1094,11 @@ def process_V9888(st):
|
||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||
ws.exlookup(d0x6a0be4d0)
|
||||
ws.exlookup(d0x6a0be4d0)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
@@ -1124,7 +1122,7 @@ def process_V4648(st):
|
||||
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a0c91a8)
|
||||
ws.exlookup(d0x6a0c91a8)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
@@ -1148,7 +1146,7 @@ def process_V5683(st):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a0d3e80)
|
||||
ws.exlookup(d0x6a0d3e80)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
@@ -1163,12 +1161,12 @@ def process_V5683(st):
|
||||
# if a<0: a=256+a
|
||||
# ax.append(ha[(a>>4)]+ha[a%16])
|
||||
# return "".join(ax)
|
||||
#
|
||||
#
|
||||
# def memhex(adr,sz):
|
||||
# emu=EmulatorHelper(currentProgram)
|
||||
# arr=emu.readMemory(getAddress(adr),sz)
|
||||
# return a2hex(arr)
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
|
||||
@@ -1254,7 +1252,7 @@ def scramble3(st,magic):
|
||||
iVar5 = iVar5 - magic
|
||||
index -= 1
|
||||
if uVar4<=-1: break
|
||||
else:
|
||||
else:
|
||||
if (offset < magic):
|
||||
iVar3 = 0
|
||||
else :
|
||||
@@ -1274,9 +1272,9 @@ def scramble3(st,magic):
|
||||
index=index-1
|
||||
iVar5 = iVar5 + magic;
|
||||
cntr += 1;
|
||||
if iVar3>=divs: break
|
||||
if iVar3>=divs: break
|
||||
offset = offset + 1
|
||||
if offset >= ((magic - 1) + divs) :break
|
||||
if offset >= ((magic - 1) + divs) :break
|
||||
return ret
|
||||
|
||||
#not sure if the third variant is used anywhere, but it is in Kindle, so I tried to add it
|
||||
@@ -1342,14 +1340,14 @@ class DrmIonVoucher(object):
|
||||
_assert(False, "Unknown lock parameter: %s" % param)
|
||||
|
||||
|
||||
# i know that version maps to scramble pretty much 1 to 1, but there was precendent where they changed it, so...
|
||||
# i know that version maps to scramble pretty much 1 to 1, but there was precendent where they changed it, so...
|
||||
sharedsecrets = [obfuscate(shared, self.version),obfuscate2(shared, self.version),obfuscate3(shared, self.version),
|
||||
process_V9708(shared), process_V1031(shared), process_V2069(shared), process_V9041(shared),
|
||||
process_V3646(shared), process_V6052(shared), process_V9479(shared), process_V9888(shared),
|
||||
process_V4648(shared), process_V5683(shared)]
|
||||
|
||||
|
||||
decrypted=False
|
||||
ex=None
|
||||
lastexception = None # type: Exception | None
|
||||
for sharedsecret in sharedsecrets:
|
||||
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||
@@ -1361,14 +1359,15 @@ class DrmIonVoucher(object):
|
||||
|
||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
||||
"Expected KeySet, got %s" % self.drmkey.gettypename())
|
||||
decrypted=True
|
||||
decrypted=True
|
||||
|
||||
print("Decryption succeeded")
|
||||
break
|
||||
except Exception as ex:
|
||||
lastexception = ex
|
||||
print("Decryption failed, trying next fallback ")
|
||||
if not decrypted:
|
||||
raise ex
|
||||
raise lastexception
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
|
||||
@@ -74,7 +74,7 @@ class KFXZipBook:
|
||||
# Belt and braces. PIDs should be unicode strings, but just in case...
|
||||
if isinstance(pid, bytes):
|
||||
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:
|
||||
break # split pid into DSN and account secret
|
||||
else:
|
||||
|
||||
@@ -374,7 +374,11 @@ class InterfacePluginAction(InterfaceAction):
|
||||
result['success'] = False
|
||||
result['fileobj'] = None
|
||||
|
||||
zin = zipfile.ZipFile(book.filename, 'r')
|
||||
try:
|
||||
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))
|
||||
for userkey in self.userkeys:
|
||||
print (_('Trying key: '), codecs.encode(userkey, 'hex'))
|
||||
|
||||
@@ -327,50 +327,36 @@ class KoboLibrary(object):
|
||||
elif sys.platform.startswith('darwin'):
|
||||
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
|
||||
elif sys.platform.startswith('linux'):
|
||||
# Since on Linux, you have to run Kobo Desktop within Wine,
|
||||
# there is no guarantee where Kobo.sqlite (and the rest of
|
||||
# the kobo directory) might be.
|
||||
#
|
||||
# It should also be noted that Kobo Desktop will store all
|
||||
# of it files in the current directory where you run it,
|
||||
# meaning that a user might accidentally create several
|
||||
# Kobo.sqlite files which are all separate and then be
|
||||
# confused why Obok can't find any of the new books. Sadly
|
||||
# there isn't a trivial way to deal with this.
|
||||
|
||||
# We cache the kobodir we find in ~/.config/calibre.
|
||||
kobodir_cache_dir = os.path.join(os.environ['HOME'], ".config", "calibre", "plugins", "obok")
|
||||
#sets ~/.config/calibre as the location to store the kobodir location info file and creates this directory if necessary
|
||||
kobodir_cache_dir = os.path.join(os.environ['HOME'], ".config", "calibre")
|
||||
if not os.path.isdir(kobodir_cache_dir):
|
||||
os.mkdir(kobodir_cache_dir)
|
||||
kobodir_cache_file = os.path.join(kobodir_cache_dir, "kobo-location")
|
||||
|
||||
#appends the name of the file we're storing the kobodir location info to the above path
|
||||
kobodir_cache_file = str(kobodir_cache_dir) + "/" + "kobo location"
|
||||
|
||||
"""if the above file does not exist, recursively searches from the root
|
||||
of the filesystem until kobodir is found and stores the location of kobodir
|
||||
in that file so this loop can be skipped in the future"""
|
||||
original_stdout = sys.stdout
|
||||
if not os.path.isfile(kobodir_cache_file):
|
||||
for root, dirs, files in os.walk('/'):
|
||||
for file in files:
|
||||
if file == 'Kobo.sqlite':
|
||||
kobo_linux_path = str(root)
|
||||
with open(kobodir_cache_file, 'w') as f:
|
||||
sys.stdout = f
|
||||
print(kobo_linux_path, end='')
|
||||
sys.stdout = original_stdout
|
||||
|
||||
try:
|
||||
# If the cached version exists and the path does really
|
||||
# contain Kobo.sqlite, use that.
|
||||
with open(kobodir_cache_file, "r") as f:
|
||||
cached_kobodir = f.read().strip()
|
||||
assert os.path.isfile(os.path.join(cached_kobodir, "Kobo.sqlite"))
|
||||
self.kobodir = cached_kobodir
|
||||
except (AssertionError, FileNotFoundError):
|
||||
# If there was no cached version, search the entire
|
||||
# filesystem tree for a directory containing
|
||||
# "Kobo.sqlite".
|
||||
#
|
||||
# We first search $HOME to avoid picking another user's
|
||||
# Kobo.sqlite file, but then fallback to / if there was
|
||||
# nothing in $HOME.
|
||||
for candidate_root in (os.environ["HOME"], "/"):
|
||||
for root, _, files in os.walk(candidate_root):
|
||||
if "Kobo.sqlite" in files:
|
||||
with open(kobodir_cache_file, "w") as f:
|
||||
f.write("%s/\n" % (root,))
|
||||
self.kobodir = root
|
||||
break
|
||||
f = open(kobodir_cache_file, 'r' )
|
||||
self.kobodir = f.read()
|
||||
|
||||
# Desktop versions use Kobo.sqlite.
|
||||
# desktop versions use Kobo.sqlite
|
||||
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
||||
# check for existence of file
|
||||
if not os.path.isfile(kobodb):
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# give up here, we haven't found anything useful
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
@@ -444,7 +430,7 @@ class KoboLibrary(object):
|
||||
macaddrs = []
|
||||
if sys.platform.startswith('win'):
|
||||
c = re.compile('\s?(' + '[0-9a-f]{2}[:\-]' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||
try:
|
||||
try:
|
||||
output = subprocess.Popen('ipconfig /all', shell=True, stdout=subprocess.PIPE, text=True).stdout
|
||||
for line in output:
|
||||
m = c.search(line)
|
||||
@@ -463,9 +449,15 @@ class KoboLibrary(object):
|
||||
for m in matches:
|
||||
# print "m:{0}".format(m[0])
|
||||
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:
|
||||
# probably linux
|
||||
|
||||
# final fallback
|
||||
# let's try ip
|
||||
c = re.compile('\s(' + '[0-9a-f]{2}:' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||
for line in os.popen('ip -br link'):
|
||||
|
||||
@@ -146,6 +146,7 @@ import inspect
|
||||
import tempfile
|
||||
import sqlite3
|
||||
import httplib
|
||||
import binascii
|
||||
|
||||
from decimal import Decimal
|
||||
import itertools
|
||||
@@ -2643,7 +2644,7 @@ class PDFParser(PSStackParser):
|
||||
except PDFNoValidXRef:
|
||||
# fallback
|
||||
self.seek(0)
|
||||
pat = re.compile(rb'^(\d+)\s+(\d+)\s+obj\b')
|
||||
pat = re.compile(br'^(\\d+)\\s+(\\d+)\\s+obj\\b')
|
||||
offsets = {}
|
||||
xref = PDFXRef()
|
||||
while 1:
|
||||
|
||||
@@ -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.
|
||||
|
||||
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
|
||||
-------------
|
||||
@@ -16,7 +18,6 @@ No customization is required, except choosing which menus will show 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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
- 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user