mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-21 21:38:56 +00:00
Compare commits
15 Commits
06648eeb1c
...
autoreleas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae77c438f | ||
|
|
abc5de018e | ||
|
|
133e67fa03 | ||
|
|
f86cff285b | ||
|
|
a553a71f45 | ||
|
|
740b46546f | ||
|
|
fb8b003444 | ||
|
|
3c12806f38 | ||
|
|
3151dbbd98 | ||
|
|
08e7ac79ca | ||
|
|
a711954323 | ||
|
|
a30405bebf | ||
|
|
901a6c091d | ||
|
|
e16748e854 | ||
|
|
06df18bea3 |
44
.github/workflows/main.yml
vendored
44
.github/workflows/main.yml
vendored
@@ -9,8 +9,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Package
|
||||
run: python3 make_release.py
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@@ -18,3 +20,45 @@ jobs:
|
||||
path: |
|
||||
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
|
||||
run: cp DeDRM_tools.zip DeDRM_alpha_${{ github.sha }}.zip
|
||||
|
||||
- name: Auto-release
|
||||
id: autorelease
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: autorelease
|
||||
token: ${{ github.token }}
|
||||
name: Automatic alpha release with latest changes
|
||||
body: |
|
||||
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.
|
||||
|
||||
Last update based on Git commit ${{ github.sha }}.
|
||||
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
|
||||
prerelease: true
|
||||
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -82,3 +82,13 @@ List of changes since the fork of Apprentice Harper's repository:
|
||||
- Fix a bug that would sometimes cause corrupted keys to be added when adding them through the config dialog (fixes #145, #134, #119, #116, #115, #109).
|
||||
- Update the README (fixes #136) to indicate that Apprentice Harper's version is no longer being updated.
|
||||
- Fix a bug where PDFs with empty arrays (`<>`) in a PDF object failed to decrypt, fixes #183.
|
||||
- Automatically strip whitespace from entered Amazon Kindle serial numbers, should fix #158.
|
||||
- Obok: Add new setting option "Add new entry" for duplicate books to always add them to the Calibre database as a new book. Fixes #148.
|
||||
- Obok: Fix where changing the Calibre UI language to some languages would cause the "duplicate book" setting to reset.
|
||||
- Fix Python3 bug in stylexml2css.php script, fixes #232.
|
||||
- PDF: Ignore invalid PDF objids unless the script is running in strict mode. Fixes some PDFs, apparently. Fixes #233.
|
||||
- Bugfix: EPUBs with remaining content in the encryption.xml after decryption weren't written correctly.
|
||||
- Support for Adobe's 'aes128-cbc-uncompressed' encryption method (fixes #242).
|
||||
- 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 a couple unicode errors for Python2 in Kindle and Nook code.
|
||||
|
||||
@@ -1326,7 +1326,7 @@ class AddSerialDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return str(self.key_ledit.text()).replace(' ', '')
|
||||
return str(self.key_ledit.text()).replace(' ', '').replace('\r', '').replace('\n', '').replace('\t', '')
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# epubfontdecrypt.py
|
||||
# Copyright © 2021 by noDRM
|
||||
# Copyright © 2021-2023 by noDRM
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
# Revision history:
|
||||
# 1 - Initial release
|
||||
# 2 - Bugfix for multiple book IDs, reported at #347
|
||||
|
||||
"""
|
||||
Decrypts / deobfuscates font files in EPUB files
|
||||
@@ -18,7 +19,7 @@ Decrypts / deobfuscates font files in EPUB files
|
||||
from __future__ import print_function
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1"
|
||||
__version__ = "2"
|
||||
|
||||
import os
|
||||
import traceback
|
||||
@@ -193,9 +194,10 @@ def decryptFontsBook(inpath, outpath):
|
||||
pass
|
||||
|
||||
try:
|
||||
identify_element = container.find(packageNS("metadata")).find(metadataDCNS("identifier"))
|
||||
if (secret_key_name is None or secret_key_name == identify_element.get("id")):
|
||||
font_master_key = identify_element.text
|
||||
identify_elements = container.find(packageNS("metadata")).findall(metadataDCNS("identifier"))
|
||||
for element in identify_elements:
|
||||
if (secret_key_name is None or secret_key_name == element.get("id")):
|
||||
font_master_key = element.text
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -54,15 +54,26 @@ def getNookLogFiles():
|
||||
paths = set()
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if sys.version_info[0] == 2:
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
else:
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
if 'USERPROFILE' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local"
|
||||
if sys.version_info[0] == 2:
|
||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local"
|
||||
else:
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local"
|
||||
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
|
||||
else:
|
||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
|
||||
if os.path.isdir(path):
|
||||
paths.add(path)
|
||||
# User Shell Folders show take precedent over Shell Folders if present
|
||||
|
||||
@@ -89,15 +89,16 @@ class Decryptor(object):
|
||||
def __init__(self, bookkey, encryption):
|
||||
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
|
||||
self._aes = AES.new(bookkey, AES.MODE_CBC, b'\x00'*16)
|
||||
encryption = etree.fromstring(encryption)
|
||||
self._encryption = etree.fromstring(encryption)
|
||||
self._encrypted = encrypted = set()
|
||||
self._encryptedForceNoDecomp = encryptedForceNoDecomp = set()
|
||||
self._otherData = otherData = set()
|
||||
|
||||
self._json_elements_to_remove = json_elements_to_remove = set()
|
||||
self._has_remaining_xml = False
|
||||
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
|
||||
enc('CipherReference'))
|
||||
for elem in encryption.findall(expr):
|
||||
for elem in self._encryption.findall(expr):
|
||||
path = elem.get('URI', None)
|
||||
encryption_type_url = (elem.getparent().getparent().find("./%s" % (enc('EncryptionMethod'))).get('Algorithm', None))
|
||||
if path is not None:
|
||||
@@ -106,6 +107,11 @@ class Decryptor(object):
|
||||
path = path.encode('utf-8')
|
||||
encrypted.add(path)
|
||||
json_elements_to_remove.add(elem.getparent().getparent())
|
||||
elif (encryption_type_url == "http://ns.adobe.com/adept/xmlenc#aes128-cbc-uncompressed"):
|
||||
# Adobe uncompressed, for stuff like video files
|
||||
path = path.encode('utf-8')
|
||||
encryptedForceNoDecomp.add(path)
|
||||
json_elements_to_remove.add(elem.getparent().getparent())
|
||||
else:
|
||||
path = path.encode('utf-8')
|
||||
otherData.add(path)
|
||||
@@ -134,14 +140,15 @@ class Decryptor(object):
|
||||
return decompressed_bytes
|
||||
|
||||
def decrypt(self, path, data):
|
||||
if path.encode('utf-8') in self._encrypted:
|
||||
if path.encode('utf-8') in self._encrypted or path.encode('utf-8') in self._encryptedForceNoDecomp:
|
||||
data = self._aes.decrypt(data)[16:]
|
||||
if type(data[-1]) != int:
|
||||
place = ord(data[-1])
|
||||
else:
|
||||
place = data[-1]
|
||||
data = data[:-place]
|
||||
data = self.decompress(data)
|
||||
if not path.encode('utf-8') in self._encryptedForceNoDecomp:
|
||||
data = self.decompress(data)
|
||||
return data
|
||||
|
||||
# check file to make check whether it's probably an Adobe Adept encrypted ePub
|
||||
|
||||
@@ -1636,13 +1636,15 @@ class PDFDocument(object):
|
||||
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" % bookkey[0])
|
||||
if len(bookkey) > 0:
|
||||
print("bookkey[0] is %d" % 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 len(bookkey) > 0:
|
||||
print("bookkey[0] is %d" % ord(bookkey[0]))
|
||||
if ebx_V == 3:
|
||||
V = 3
|
||||
else:
|
||||
@@ -1708,13 +1710,15 @@ class PDFDocument(object):
|
||||
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" % bookkey[0])
|
||||
if len(bookkey) > 0:
|
||||
print("bookkey[0] is %d" % 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" % bookkey[0])
|
||||
if len(bookkey) > 0:
|
||||
print("bookkey[0] is %d" % bookkey[0])
|
||||
if ebx_V == 3:
|
||||
V = 3
|
||||
else:
|
||||
@@ -1827,7 +1831,19 @@ class PDFDocument(object):
|
||||
try:
|
||||
obj = objs[i]
|
||||
except IndexError:
|
||||
raise PDFSyntaxError('Invalid object number: objid=%r' % (objid))
|
||||
# This IndexError used to just raise an exception.
|
||||
# Unfortunately that seems to break some PDFs, see this issue:
|
||||
# https://github.com/noDRM/DeDRM_tools/issues/233
|
||||
# I'm not sure why this is the case, but lets try only raising that exception
|
||||
# when in STRICT mode, and make it a warning otherwise.
|
||||
if STRICT:
|
||||
raise PDFSyntaxError('Invalid object number: objid=%r' % (objid))
|
||||
|
||||
print('Invalid object number: objid=%r' % (objid))
|
||||
print("Continuing anyways?")
|
||||
print("If the resulting PDF is corrupted, please open a bug report.")
|
||||
return None
|
||||
|
||||
if isinstance(obj, PDFStream):
|
||||
obj.set_objid(objid, 0)
|
||||
else:
|
||||
|
||||
@@ -57,6 +57,7 @@ except ImportError:
|
||||
# Windows-friendly choice: pylzma wheels
|
||||
import pylzma as lzma
|
||||
|
||||
from kfxtables import *
|
||||
|
||||
TID_NULL = 0
|
||||
TID_BOOLEAN = 1
|
||||
@@ -769,6 +770,7 @@ def pkcs7unpad(msg, blocklen):
|
||||
|
||||
|
||||
# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
|
||||
# 4-digit versions use their own obfuscation/scramble. It does not seem to depend on the "word" and number
|
||||
OBFUSCATION_TABLE = {
|
||||
"V1": (0x00, None),
|
||||
"V2": (0x05, b'Antidisestablishmentarianism'),
|
||||
@@ -779,26 +781,26 @@ OBFUSCATION_TABLE = {
|
||||
"V7": (0x05, b'\x10\x1bJ\x18\nh!\x10"\x03>Z\'\r\x01]W\x06\x1c\x1e?\x0f\x13'),
|
||||
"V8": (0x09, b"K\x0c6\x1d\x1a\x17pO}Rk\x1d'w1^\x1f$\x1c{C\x02Q\x06\x1d`"),
|
||||
"V9": (0x05, b'X.\x0eW\x1c*K\x12\x12\t\n\n\x17Wx\x01\x02Yf\x0f\x18\x1bVXPi\x01'),
|
||||
"V10": (0x07, b'z3\n\x039\x12\x13`\x06=v,\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||
"V10": (0x07, b'z3\n\x039\x12\x13`\x06=v;\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||
"V11": (0x05, b'L=\nhVm\x07go\n6\x14\x06\x16L\r\x02\x0b\x0c\x1b\x04#p\t'),
|
||||
"V12": (0x06, b',n\x1d\rl\x13\x1c\x13\x16p\x14\x07U\x0c\x1f\x19w\x16\x16\x1d5T'),
|
||||
"V12": (0x06, b';n\x1d\rl\x13\x1c\x13\x16p\x14\x07U\x0c\x1f\x19w\x16\x16\x1d5T'),
|
||||
"V13": (0x07, b'I\x05\t\x08\x03r)\x01$N\x0fr3n\x0b062D\x0f\x13'),
|
||||
"V14": (0x05, b"\x03\x02\x1c9\x19\x15\x15q\x1057\x08\x16\x0cF\x1b.Fw\x01\x12\x03\x13\x02\x17S'hk6"),
|
||||
"V15": (0x0A, b'&,4B\x1dcI\x0bU\x03I\x07\x04\x1c\t\x05c\x07%ws\x0cj\t\x1a\x08\x0f'),
|
||||
"V16": (0x0A, b'\x06\x18`h,b><\x06PqR\x02Zc\x034\n\x16\x1e\x18\x06#e'),
|
||||
"V16": (0x0A, b'\x06\x18`h;b><\x06PqR\x02Zc\x034\n\x16\x1e\x18\x06#e'),
|
||||
"V17": (0x07, b'y\r\x12\x08fw.[\x02\t\n\x13\x11\x0c\x11b\x1e8L\x10(\x13<Jx6c\x0f'),
|
||||
"V18": (0x07, b'I\x0b\x0e,\x19\x1aIa\x10s\x19g\\\x1b\x11!\x18yf\x0f\t\x1d7[bSp\x03'),
|
||||
"V18": (0x07, b'I\x0b\x0e;\x19\x1aIa\x10s\x19g\\\x1b\x11!\x18yf\x0f\t\x1d7[bSp\x03'),
|
||||
"V19": (0x05, b'\n6>)N\x02\x188\x016s\x13\x14\x1b\x16jeN\n\x146\x04\x18\x1c\x0c\x19\x1f,\x02]'),
|
||||
"V20": (0x08, b'_\r\x01\x12]\\\x14*\x17i\x14\r\t!\x1e,~hZ\x12jK\x17\x1e*1'),
|
||||
"V20": (0x08, b'_\r\x01\x12]\\\x14*\x17i\x14\r\t!\x1e;~hZ\x12jK\x17\x1e*1'),
|
||||
"V21": (0x07, b'e\x1d\x19|\ty\x1di|N\x13\x0e\x04\x1bj<h\x13\x15k\x12\x08=\x1f\x16~\x13l'),
|
||||
"V22": (0x08, b'?\x17yi$k7Pc\tEo\x0c\x07\x07\t\x1f,*i\x12\x0cI0\x10I\x1a?2\x04'),
|
||||
"V23": (0x08, b'\x16+db\x13\x04\x18\rc%\x14\x17\x0f\x13F\x0c[\t9\x1ay\x01\x1eH'),
|
||||
"V24": (0x06, b'|6\\\x1a\r\x10\nP\x07\x0fu\x1f\t,\rr`uv\\~55\x11]N'),
|
||||
"V25": (0x09, b'\x07\x14w\x1e,^y\x01:\x08\x07\x1fr\tU#j\x16\x12\x1eB\x04\x16=\x06fZ\x07\x02\x06'),
|
||||
"V24": (0x06, b'|6\\\x1a\r\x10\nP\x07\x0fu\x1f\t;\rr`uv\\~55\x11]N'),
|
||||
"V25": (0x09, b'\x07\x14w\x1e;^y\x01:\x08\x07\x1fr\tU#j\x16\x12\x1eB\x04\x16=\x06fZ\x07\x02\x06'),
|
||||
"V26": (0x06, b'\x03IL\x1e"K\x1f\x0f\x1fp0\x01`X\x02z0`\x03\x0eN\x07'),
|
||||
"V27": (0x07, b'Xk\x10y\x02\x18\x10\x17\x1d,\x0e\x05e\x10\x15"e\x0fh(\x06s\x1c\x08I\x0c\x1b\x0e'),
|
||||
"V28": (0x0A, b'6P\x1bs\x0f\x06V.\x1cM\x14\x02\n\x1b\x07{P0:\x18zaU\x05'),
|
||||
"V9708": (0x05, b'\x1diIm\x08a\x17\x1e!am\x1d\x1aQ.\x16!\x06*\}x04\x11\t\x06\x04?'),
|
||||
"V9708": (0x05, b'\x1diIm\x08a\x17\x1e!am\x1d\x1aQ.\x16!\x06*\x04\x11\t\x06\x04?'),
|
||||
"V1031": (0x08, b'Antidisestablishmentarianism'),
|
||||
"V2069": (0x07, b'Floccinaucinihilipilification'),
|
||||
"V9041": (0x06, b'>\x14\x0c\x12\x10-\x13&\x18U\x1d\x05Rlt\x03!\x19\x1b\x13\x04]Y\x19,\t\x1b'),
|
||||
@@ -807,10 +809,367 @@ OBFUSCATION_TABLE = {
|
||||
"V9479": (0x09, b'\x10\x1bJ\x18\nh!\x10"\x03>Z\'\r\x01]W\x06\x1c\x1e?\x0f\x13'),
|
||||
"V9888": (0x05, b"K\x0c6\x1d\x1a\x17pO}Rk\x1d'w1^\x1f$\x1c{C\x02Q\x06\x1d`"),
|
||||
"V4648": (0x07, b'X.\x0eW\x1c*K\x12\x12\t\n\n\x17Wx\x01\x02Yf\x0f\x18\x1bVXPi\x01'),
|
||||
"V5683": (0x05, b'z3\n\x039\x12\x13`\x06=v,\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||
"V5683": (0x05, b'z3\n\x039\x12\x13`\x06=v;\x02MTK\x1e%}L\x1c\x1f\x15\x0c\x11\x02\x0c\n8\x17p'),
|
||||
}
|
||||
|
||||
|
||||
#common str: "PIDv3AESAES/CBC/PKCS5PaddingHmacSHA256"
|
||||
class workspace(object):
|
||||
def __init__(self,initial_list):
|
||||
self.work=initial_list
|
||||
def shuffle(self,shuflist):
|
||||
ll=len(shuflist)
|
||||
rt=[]
|
||||
for i in range(ll):
|
||||
rt.append(self.work[shuflist[i]])
|
||||
self.work=rt
|
||||
def sbox(self,table,matrix,skplist=[]): #table is list of 4-byte integers
|
||||
offset=0
|
||||
nwork=list(self.work)
|
||||
wo=0
|
||||
toff=0
|
||||
while offset<0x6000:
|
||||
uv5=table[toff+nwork[wo+0]]
|
||||
uv1=table[toff+nwork[wo+1]+0x100]
|
||||
uv2=table[toff+nwork[wo+2]+0x200]
|
||||
uv3=table[toff+nwork[wo+3]+0x300]
|
||||
moff=0
|
||||
if 0 in skplist:
|
||||
moff+=0x400
|
||||
else:
|
||||
nib1=matrix[moff+offset+(uv1>>0x1c)|( (uv5>>0x18)&0xf0)]
|
||||
moff+=0x100
|
||||
nib2=matrix[moff+offset+(uv3>>0x1c)|( (uv2>>0x18)&0xf0)]
|
||||
moff+=0x100
|
||||
nib3=matrix[moff+offset+((uv1>>0x18)&0xf) |( (uv5>>0x14)&0xf0)]
|
||||
moff+=0x100
|
||||
nib4=matrix[moff+offset+((uv3>>0x18)&0xf) |( (uv2>>0x14)&0xf0)]
|
||||
moff+=0x100
|
||||
rnib1=matrix[moff+offset+nib1*0x10+nib2]
|
||||
moff+=0x100
|
||||
rnib2=matrix[moff+offset+nib3*0x10+nib4]
|
||||
moff+=0x100
|
||||
nwork[wo+0]=rnib1*0x10+rnib2
|
||||
if 1 in skplist:
|
||||
moff+=0x400
|
||||
else:
|
||||
nib1=matrix[moff+offset+((uv1>>0x14)&0xf)|( (uv5>>0x10)&0xf0)]
|
||||
moff+=0x100
|
||||
nib2=matrix[moff+offset+((uv3>>0x14)&0xf)|( (uv2>>0x10)&0xf0)]
|
||||
moff+=0x100
|
||||
nib3=matrix[moff+offset+((uv1>>0x10)&0xf) |( (uv5>>0xc)&0xf0)]
|
||||
moff+=0x100
|
||||
nib4=matrix[moff+offset+((uv3>>0x10)&0xf) |( (uv2>>0xc)&0xf0)]
|
||||
moff+=0x100
|
||||
|
||||
rnib1=matrix[moff+offset+nib1*0x10+nib2]
|
||||
moff+=0x100
|
||||
rnib2=matrix[moff+offset+nib3*0x10+nib4]
|
||||
moff+=0x100
|
||||
nwork[wo+1]=rnib1*0x10+rnib2
|
||||
if 2 in skplist:
|
||||
moff+=0x400
|
||||
else:
|
||||
nib1=matrix[moff+offset+((uv1>>0xc)&0xf)|( (uv5>>0x8)&0xf0)]
|
||||
moff+=0x100
|
||||
nib2=matrix[moff+offset+((uv3>>0xc)&0xf)|( (uv2>>0x8)&0xf0)]
|
||||
moff+=0x100
|
||||
nib3=matrix[moff+offset+((uv1>>0x8)&0xf) |( (uv5>>0x4)&0xf0)]
|
||||
moff+=0x100
|
||||
nib4=matrix[moff+offset+((uv3>>0x8)&0xf) |( (uv2>>0x4)&0xf0)]
|
||||
moff+=0x100
|
||||
rnib1=matrix[moff+offset+nib1*0x10+nib2]
|
||||
moff+=0x100
|
||||
rnib2=matrix[moff+offset+nib3*0x10+nib4]
|
||||
moff+=0x100
|
||||
nwork[wo+2]=rnib1*0x10+rnib2
|
||||
if 3 in skplist:
|
||||
moff+=0x400
|
||||
else:
|
||||
nib1=matrix[moff+offset+((uv1>>0x4)&0xf)|( (uv5)&0xf0)]
|
||||
moff+=0x100
|
||||
nib2=matrix[moff+offset+((uv3>>0x4)&0xf)|( (uv2)&0xf0)]
|
||||
moff+=0x100
|
||||
nib3=matrix[moff+offset+((uv1)&0xf)|( (uv5<<4)&0xf0) ]
|
||||
moff+=0x100
|
||||
nib4=matrix[moff+offset+((uv3)&0xf)|( (uv2<<4)&0xf0) ]
|
||||
moff+=0x100
|
||||
##############
|
||||
rnib1=matrix[moff+offset+nib1*0x10+nib2]
|
||||
moff+=0x100
|
||||
rnib2=matrix[moff+offset+nib3*0x10+nib4]
|
||||
moff+=0x100
|
||||
nwork[wo+3]=rnib1*0x10+rnib2
|
||||
offset = offset + 0x1800
|
||||
wo+=4
|
||||
toff+=0x400
|
||||
self.work=nwork
|
||||
def lookup(self,ltable):
|
||||
for a in range(len(self.work)):
|
||||
self.work[a]=ltable[a]
|
||||
def exlookup(self,ltable):
|
||||
lookoffs=0
|
||||
for a in range(len(self.work)):
|
||||
self.work[a]=ltable[self.work[a]+lookoffs]
|
||||
lookoffs+=0x100
|
||||
def mask(self, chunk):
|
||||
out=[]
|
||||
for a in range(len(chunk)):
|
||||
self.work[a]=self.work[a]^chunk[a]
|
||||
out.append(self.work[a])
|
||||
return out
|
||||
|
||||
def process_V9708(st):
|
||||
#e9c457a7dae6aa24365e7ef219b934b17ed58ee7d5329343fc3aea7860ed51f9a73de14351c9
|
||||
ws=workspace([0x11]*16)
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a06ea70,d0x6a0dab50)
|
||||
ws.sbox(d0x6a073a70,d0x6a0dab50)
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a072a70)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16;
|
||||
return bytes(out)
|
||||
|
||||
def process_V1031(st):
|
||||
#d53efea7fdd0fda3e1e0ebbae87cad0e8f5ef413c471c3ae81f39222a9ec8b8ed582e045918c
|
||||
ws=workspace([0x06,0x18,0x60,0x68,0x3b,0x62,0x3e,0x3c,0x06,0x50,0x71,0x52,0x02,0x5a,0x63,0x03])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0797c0,d0x6a0dab50,[3])
|
||||
ws.sbox(d0x6a07e7c0,d0x6a0dab50,[3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0797c0,d0x6a0dab50,[3])
|
||||
ws.sbox(d0x6a07e7c0,d0x6a0dab50,[3])
|
||||
ws.exlookup(d0x6a07d7c0)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
#break
|
||||
return bytes(out)
|
||||
|
||||
def process_V2069(st):
|
||||
#8e6196d754a304c9354e91b5d79f07b048026d31c7373a8691e513f2c802c706742731caa858
|
||||
ws=workspace([0x79,0x0d,0x12,0x08,0x66,0x77,0x2e,0x5b,0x02,0x09,0x0a,0x13,0x11,0x0c,0x11,0x62])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a084498,d0x6a0dab50,[2])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a089498,d0x6a0dab50,[2])
|
||||
ws.sbox(d0x6a089498,d0x6a0dab50,[2])
|
||||
ws.sbox(d0x6a084498,d0x6a0dab50,[2])
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a088498)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def process_V9041(st):
|
||||
#11f7db074b24e560dfa6fae3252b383c3b936e51f6ded570dc936cb1da9f4fc4a97ec686e7d8
|
||||
ws=workspace([0x49,0x0b,0x0e,0x3b,0x19,0x1a,0x49,0x61,0x10,0x73,0x19,0x67,0x5c,0x1b,0x11,0x21])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a094170,d0x6a0dab50,[1])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a08f170,d0x6a0dab50,[1])
|
||||
ws.sbox(d0x6a08f170,d0x6a0dab50,[1])
|
||||
ws.sbox(d0x6a094170,d0x6a0dab50,[1])
|
||||
|
||||
ws.exlookup(d0x6a093170)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
#break
|
||||
return bytes(out)
|
||||
|
||||
def process_V3646(st):
|
||||
#d468aa362b44479282291983243b38197c4b4aa24c2c58e62c76ec4b81e08556ca0c54301664
|
||||
ws=workspace([0x0a,0x36,0x3e,0x29,0x4e,0x02,0x18,0x38,0x01,0x36,0x73,0x13,0x14,0x1b,0x16,0x6a])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a099e48,d0x6a0dab50,[2,3])
|
||||
ws.sbox(d0x6a09ee48,d0x6a0dab50,[2,3])
|
||||
ws.sbox(d0x6a09ee48,d0x6a0dab50,[2,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a099e48,d0x6a0dab50,[2,3])
|
||||
ws.sbox(d0x6a099e48,d0x6a0dab50,[2,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a09ee48,d0x6a0dab50,[2,3])
|
||||
ws.exlookup(d0x6a09de48)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def process_V6052(st):
|
||||
#d683c8c4e4f46ae45812196f37e218eabce0fae08994f25fabb01d3e569b8bf3866b99d36f57
|
||||
ws=workspace([0x5f,0x0d,0x01,0x12,0x5d,0x5c,0x14,0x2a,0x17,0x69,0x14,0x0d,0x09,0x21,0x1e,0x3b])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0a4b20,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0a4b20,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0a9b20,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0a9b20,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0a9b20,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0a4b20,d0x6a0dab50,[1,3])
|
||||
|
||||
ws.exlookup(d0x6a0a8b20)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
def process_V9479(st):
|
||||
#925635db434bccd3f4791eb87b89d2dfc7c93be06e794744eb9de58e6d721e696980680ab551
|
||||
ws=workspace([0x65,0x1d,0x19,0x7c,0x09,0x79,0x1d,0x69,0x7c,0x4e,0x13,0x0e,0x04,0x1b,0x6a,0x3c ])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a0af7f8,d0x6a0dab50,[1,2,3])
|
||||
ws.sbox(d0x6a0af7f8,d0x6a0dab50,[1,2,3])
|
||||
ws.sbox(d0x6a0b47f8,d0x6a0dab50,[1,2,3])
|
||||
ws.sbox(d0x6a0af7f8,d0x6a0dab50,[1,2,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0b47f8,d0x6a0dab50,[1,2,3])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0b47f8,d0x6a0dab50,[1,2,3])
|
||||
ws.exlookup(d0x6a0b37f8)
|
||||
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
def process_V9888(st):
|
||||
#54c470723f8c105ba0186b6319050869de673ce31a5ec15d4439921d4cd05c5e860cb2a41fea
|
||||
ws=workspace([0x3f,0x17,0x79,0x69,0x24,0x6b,0x37,0x50,0x63,0x09,0x45,0x6f,0x0c,0x07,0x07,0x09])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0bf4d0,d0x6a0dab50,[1,2])
|
||||
ws.sbox(d0x6a0ba4d0,d0x6a0dab50,[1,2])
|
||||
ws.exlookup(d0x6a0be4d0)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
def process_V4648(st):
|
||||
#705bd4cd8b61d4596ef4ca40774d68e71f1f846c6e94bd23fd26e5c127e0beaa650a50171f1b
|
||||
ws=workspace([0x16,0x2b,0x64,0x62,0x13,0x04,0x18,0x0d,0x63,0x25,0x14,0x17,0x0f,0x13,0x46,0x0c])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a0ca1a8,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0ca1a8,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0ca1a8,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
||||
ws.sbox(d0x6a0c51a8,d0x6a0dab50,[1,3])
|
||||
ws.shuffle(repl)
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a0c91a8)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
def process_V5683(st):
|
||||
#1f5af733423e5104afb9d5594e682ecf839a776257f33747c9beee671c57ab3f84943f69d8fd
|
||||
ws=workspace([0x7c,0x36,0x5c,0x1a,0x0d,0x10,0x0a,0x50,0x07,0x0f,0x75,0x1f,0x09,0x3b,0x0d,0x72])
|
||||
repl=[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]
|
||||
remln=len(st)
|
||||
sto=0
|
||||
out=[]
|
||||
while(remln>0):
|
||||
ws.sbox(d0x6a0d4e80,d0x6a0dab50,[])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
||||
ws.sbox(d0x6a0d4e80,d0x6a0dab50,[])
|
||||
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
||||
ws.sbox(d0x6a0d4e80,d0x6a0dab50,[])
|
||||
ws.shuffle(repl)
|
||||
ws.sbox(d0x6a0cfe80,d0x6a0dab50,[])
|
||||
ws.shuffle(repl)
|
||||
ws.exlookup(d0x6a0d3e80)
|
||||
dat=ws.mask(st[sto:sto+16])
|
||||
out+=dat
|
||||
sto+=16
|
||||
remln-=16
|
||||
return bytes(out)
|
||||
|
||||
|
||||
# def a2hex(arr):
|
||||
# ax=[]
|
||||
# ha="0123456789abcdef"
|
||||
# for a in arr:
|
||||
# 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)
|
||||
#
|
||||
|
||||
|
||||
|
||||
|
||||
# obfuscate shared secret according to the VoucherEnvelope version
|
||||
def obfuscate(secret, version):
|
||||
if version == 1: # v1 does not use obfuscation
|
||||
@@ -835,6 +1194,107 @@ def obfuscate(secret, version):
|
||||
return obfuscated
|
||||
|
||||
|
||||
|
||||
# scramble() and obfuscate2() from https://github.com/andrewc12/DeDRM_tools/commit/d9233d61f00d4484235863969919059f4d0b2057
|
||||
|
||||
def scramble(st,magic):
|
||||
ret=bytearray(len(st))
|
||||
padlen=len(st)
|
||||
for counter in range(len(st)):
|
||||
ivar2=(padlen//2)-2*(counter%magic)+magic+counter-1
|
||||
ret[ivar2%padlen]=st[counter]
|
||||
return ret
|
||||
|
||||
|
||||
def obfuscate2(secret, version):
|
||||
if version == 1: # v1 does not use obfuscation
|
||||
return secret
|
||||
magic, word = OBFUSCATION_TABLE["V%d" % version]
|
||||
# extend secret so that its length is divisible by the magic number
|
||||
if len(secret) % magic != 0:
|
||||
secret = secret + b'\x00' * (magic - len(secret) % magic)
|
||||
obfuscated = bytearray(len(secret))
|
||||
wordhash = bytearray(hashlib.sha256(word).digest()[16:])
|
||||
#print(wordhash.hex())
|
||||
shuffled = bytearray(scramble(secret,magic))
|
||||
for i in range(0, len(secret)):
|
||||
obfuscated[i] = shuffled[i] ^ wordhash[i % 16]
|
||||
return obfuscated
|
||||
|
||||
# scramble3() and obfuscate3() from https://github.com/Satsuoni/DeDRM_tools/commit/da6b6a0c911b6d45fe1b13042b690daebc1cc22f
|
||||
|
||||
def scramble3(st,magic):
|
||||
ret=bytearray(len(st))
|
||||
padlen=len(st)
|
||||
divs = padlen // magic
|
||||
cntr = 0
|
||||
iVar6 = 0
|
||||
offset = 0
|
||||
if (0 < ((magic - 1) + divs)):
|
||||
while True:
|
||||
if (offset & 1) == 0 :
|
||||
uVar4 = divs - 1
|
||||
if offset < divs:
|
||||
iVar3 = 0
|
||||
uVar4 = offset
|
||||
else:
|
||||
iVar3 = (offset - divs) + 1
|
||||
if uVar4>=0:
|
||||
iVar5 = uVar4 * magic
|
||||
index = ((padlen - 1) - cntr)
|
||||
while True:
|
||||
if (magic <= iVar3): break
|
||||
ret[index] = st[iVar3 + iVar5]
|
||||
iVar3 = iVar3 + 1
|
||||
cntr = cntr + 1
|
||||
uVar4 = uVar4 - 1
|
||||
iVar5 = iVar5 - magic
|
||||
index -= 1
|
||||
if uVar4<=-1: break
|
||||
else:
|
||||
if (offset < magic):
|
||||
iVar3 = 0
|
||||
else :
|
||||
iVar3 = (offset - magic) + 1
|
||||
if (iVar3 < divs):
|
||||
uVar4 = offset
|
||||
if (magic <= offset):
|
||||
uVar4 = magic - 1
|
||||
|
||||
index = ((padlen - 1) - cntr)
|
||||
iVar5 = iVar3 * magic
|
||||
while True:
|
||||
if (uVar4 < 0) : break
|
||||
iVar3 += 1
|
||||
ret[index] = st[uVar4 + iVar5]
|
||||
uVar4 -= 1
|
||||
index=index-1
|
||||
iVar5 = iVar5 + magic;
|
||||
cntr += 1;
|
||||
if iVar3>=divs: break
|
||||
offset = offset + 1
|
||||
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
|
||||
def obfuscate3(secret, version):
|
||||
if version == 1: # v1 does not use obfuscation
|
||||
return secret
|
||||
magic, word = OBFUSCATION_TABLE["V%d" % version]
|
||||
# extend secret so that its length is divisible by the magic number
|
||||
if len(secret) % magic != 0:
|
||||
secret = secret + b'\x00' * (magic - len(secret) % magic)
|
||||
#secret = bytearray(secret)
|
||||
obfuscated = bytearray(len(secret))
|
||||
wordhash = bytearray(hashlib.sha256(word).digest())
|
||||
#print(wordhash.hex())
|
||||
shuffled=bytearray(scramble3(secret,magic))
|
||||
#print(shuffled)
|
||||
# shuffle secret and xor it with the first half of the word hash
|
||||
for i in range(0, len(secret)):
|
||||
obfuscated[i] = shuffled[i] ^ wordhash[i % 16]
|
||||
return obfuscated
|
||||
|
||||
class DrmIonVoucher(object):
|
||||
envelope = None
|
||||
version = None
|
||||
@@ -878,18 +1338,34 @@ class DrmIonVoucher(object):
|
||||
else:
|
||||
_assert(False, "Unknown lock parameter: %s" % param)
|
||||
|
||||
sharedsecret = obfuscate(shared, self.version)
|
||||
|
||||
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||
b = aes.decrypt(self.ciphertext)
|
||||
b = pkcs7unpad(b, 16)
|
||||
# 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
|
||||
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])
|
||||
try:
|
||||
b = aes.decrypt(self.ciphertext)
|
||||
b = pkcs7unpad(b, 16)
|
||||
self.drmkey = BinaryIonParser(BytesIO(b))
|
||||
addprottable(self.drmkey)
|
||||
|
||||
self.drmkey = BinaryIonParser(BytesIO(b))
|
||||
addprottable(self.drmkey)
|
||||
_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
|
||||
|
||||
_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())
|
||||
print("Decryption succeeded")
|
||||
break
|
||||
except Exception as ex:
|
||||
print("Decryption failed, trying next fallback ")
|
||||
if not decrypted:
|
||||
raise ex
|
||||
|
||||
self.drmkey.stepin()
|
||||
while self.drmkey.hasnext():
|
||||
|
||||
5771
DeDRM_plugin/kfxtables.py
Normal file
5771
DeDRM_plugin/kfxtables.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -279,7 +279,10 @@ if iswindows:
|
||||
path = ""
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if sys.version_info[0] == 2:
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
else:
|
||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
# this is just another alternative.
|
||||
# path = getEnvironmentVariable('LOCALAPPDATA')
|
||||
if not os.path.isdir(path):
|
||||
|
||||
@@ -181,7 +181,7 @@ class DocParser(object):
|
||||
print("Scale not defined!")
|
||||
scale = 1.0
|
||||
|
||||
if val == "":
|
||||
if not val:
|
||||
val = 0
|
||||
|
||||
if not ((attr == b'hang') and (int(val) == 0)):
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
# topazextract.py
|
||||
# Mostly written by some_updates based on code from many others
|
||||
|
||||
|
||||
@@ -237,7 +237,10 @@ class InterfacePluginAction(InterfaceAction):
|
||||
|
||||
:param books_to_add: List of calibre bookmaps (created in get_decrypted_kobo_books)
|
||||
'''
|
||||
added = self.db.add_books(books_to_add, add_duplicates=False, run_hooks=False)
|
||||
|
||||
cfg_add_duplicates = (cfg['finding_homes_for_formats'] == 'Add new entry')
|
||||
|
||||
added = self.db.add_books(books_to_add, add_duplicates=cfg_add_duplicates, run_hooks=False)
|
||||
if len(added[0]):
|
||||
# Record the id(s) that got added
|
||||
for id in added[0]:
|
||||
|
||||
@@ -39,8 +39,13 @@ class ConfigWidget(QWidget):
|
||||
self.find_homes = QComboBox()
|
||||
self.find_homes.setToolTip(_('<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten'))
|
||||
layout.addWidget(self.find_homes)
|
||||
self.find_homes.addItems([_('Ask'), _('Always'), _('Never')])
|
||||
|
||||
self.find_homes.addItems([_('Ask'), _('Always'), _('Never'), _('Add new entry')])
|
||||
|
||||
index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats'])
|
||||
if index == -1:
|
||||
index = self.find_homes.findText(_(plugin_prefs['finding_homes_for_formats']))
|
||||
|
||||
self.find_homes.setCurrentIndex(index)
|
||||
|
||||
self.serials_button = QtGui.QPushButton(self)
|
||||
@@ -69,7 +74,24 @@ class ConfigWidget(QWidget):
|
||||
|
||||
|
||||
def save_settings(self):
|
||||
plugin_prefs['finding_homes_for_formats'] = self.find_homes.currentText()
|
||||
|
||||
|
||||
# Make sure the config file string is *always* english.
|
||||
find_homes = None
|
||||
if self.find_homes.currentText() == _('Ask'):
|
||||
find_homes = 'Ask'
|
||||
elif self.find_homes.currentText() == _('Always'):
|
||||
find_homes = 'Always'
|
||||
elif self.find_homes.currentText() == _('Never'):
|
||||
find_homes = 'Never'
|
||||
elif self.find_homes.currentText() == _('Add new entry'):
|
||||
find_homes = 'Add new entry'
|
||||
|
||||
if find_homes is None:
|
||||
# Fallback
|
||||
find_homes = self.find_homes.currentText()
|
||||
|
||||
plugin_prefs['finding_homes_for_formats'] = find_homes
|
||||
plugin_prefs['kobo_serials'] = self.tmpserials
|
||||
plugin_prefs['kobo_directory'] = self.kobodirectory
|
||||
|
||||
|
||||
@@ -312,11 +312,17 @@ class KoboLibrary(object):
|
||||
if sys.getwindowsversion().major > 5:
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
self.kobodir = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if sys.version_info[0] == 2:
|
||||
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
else:
|
||||
self.kobodir = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
||||
if (self.kobodir == u""):
|
||||
if 'USERPROFILE' in os.environ.keys():
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data")
|
||||
if sys.version_info[0] == 2:
|
||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), "Local Settings", "Application Data")
|
||||
else:
|
||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data")
|
||||
self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
|
||||
elif sys.platform.startswith('darwin'):
|
||||
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
|
||||
|
||||
@@ -3,6 +3,8 @@ 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.
|
||||
|
||||
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).
|
||||
|
||||
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 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.
|
||||
|
||||
Reference in New Issue
Block a user