First Applescript for calling mobidedrm python script
This commit is contained in:
parent
87630e2bd4
commit
1096e48342
|
@ -0,0 +1,55 @@
|
||||||
|
Mobipocket Unlocker AppleScript Version 3
|
||||||
|
|
||||||
|
How to get Drag&Drop decryption of DRM-encumbered Mobipocket eBook files.
|
||||||
|
|
||||||
|
You'll need the MobiDeDRM.py python script, as well as an installed version 2.4 or later of python. If you have Mac OS X Leopard (10.5) you already have a suitable version of python installed as part of Leopard.
|
||||||
|
|
||||||
|
Control-click the script and select "Show Package Contents" from the contextual menu. Copy the python script, which must be called "MobiDeDRM.py" into the Resources folder inside the Contents folder. (NB not into the Scripts folder - that's where the Applescript part is stored.)
|
||||||
|
|
||||||
|
Close the package, and you now have a drag&drop Mobipocket decrypter.
|
||||||
|
|
||||||
|
You can use the AppleScript ScriptEditor application to put your Mobipocket code into the script to save you having to enter it in the dialog all the time.
|
||||||
|
|
||||||
|
If you run the script directly, you'll be asked to select a folder of Mobipocket files, and then your PID. The script will attempt to unlock all Mobipocket files in the folder selected, including files in subfolders.
|
||||||
|
|
||||||
|
If you drag and drop files and/or folders onto the script, you will be asked for your PID and then the script will attampt to unlock all the Mobipocket files dragged directly, and all Mobipocket files in the dragged folders (& subfolders).
|
||||||
|
|
||||||
|
If the Python script returns an error, the AppleScript will report it, otherwise it works without any visible feedback.
|
||||||
|
|
||||||
|
The MobiDeDRM.py scripts out there don't work perfectly. If you get the 0.02 version of the script, you can fix up a few problems by apply these changes:
|
||||||
|
|
||||||
|
After line 63:
|
||||||
|
[tab][tab]bitpos, result = 0,0
|
||||||
|
|
||||||
|
add in the following two lines:
|
||||||
|
[tab][tab]if size <= 0:
|
||||||
|
[tab][tab][tab]return result
|
||||||
|
|
||||||
|
After line 135:
|
||||||
|
[tab][tab]records, = struct.unpack('>H', sect[0x8:0x8+2])
|
||||||
|
|
||||||
|
add in the following three lines
|
||||||
|
[tab][tab]mobi_length, = struct.unpack('>L',sect[0x14:0x18])
|
||||||
|
[tab][tab]extra_data_flags = 0
|
||||||
|
[tab][tab]if mobi_length >= 0xE4:
|
||||||
|
|
||||||
|
(note that tricky comma after the first instance of mobi_length)
|
||||||
|
|
||||||
|
Add a tab to line 136:
|
||||||
|
[tab][tab]extra_data_flags, = struct.unpack('>L', sect[0xF0:0xF4])
|
||||||
|
|
||||||
|
to make it
|
||||||
|
[tab][tab][tab]extra_data_flags, = struct.unpack('>L', sect[0xF0:0xF4])
|
||||||
|
|
||||||
|
and change line 165:
|
||||||
|
print "MobiDeDrm v0.02. Copyright (c) 2008 The Dark Reverser"
|
||||||
|
|
||||||
|
to
|
||||||
|
print "MobiDeDrm v0.04. Copyright (c) 2008 The Dark Reverser"
|
||||||
|
|
||||||
|
just so that you don't get confused at the command line.
|
||||||
|
|
||||||
|
|
||||||
|
Oh -- [tab] just means a single tab character - not the five literal characters [tab].
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleAllowMixedLocalizations</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>*</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeOSTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>****</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>droplet</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>droplet</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>Mobipocket Unlocker 4</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>dplt</string>
|
||||||
|
<key>LSRequiresCarbon</key>
|
||||||
|
<true/>
|
||||||
|
<key>WindowState</key>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>ScriptWindowState</string>
|
||||||
|
<key>positionOfDivider</key>
|
||||||
|
<real>435</real>
|
||||||
|
<key>savedFrame</key>
|
||||||
|
<string>53 78 661 691 0 0 1280 778 </string>
|
||||||
|
<key>selectedTabView</key>
|
||||||
|
<string>result</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
APPLdplt
|
|
@ -0,0 +1,186 @@
|
||||||
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
|
# For example, ActiveState Python, which exists for windows.
|
||||||
|
#
|
||||||
|
# Changelog
|
||||||
|
# 0.01 - Initial version
|
||||||
|
# 0.02 - Huffdic compressed books were not properly decrypted
|
||||||
|
# 0.03 - fix 0.02 to work with all Mobipocket eBooks
|
||||||
|
# 0.04 - Wasn't checking MOBI header length
|
||||||
|
|
||||||
|
import sys,struct,binascii
|
||||||
|
|
||||||
|
class DrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
#implementation of Pukall Cipher 1
|
||||||
|
def PC1(key, src, decryption=True):
|
||||||
|
sum1 = 0;
|
||||||
|
sum2 = 0;
|
||||||
|
keyXorVal = 0;
|
||||||
|
if len(key)!=16:
|
||||||
|
print "Bad key length!"
|
||||||
|
return None
|
||||||
|
wkey = []
|
||||||
|
for i in xrange(8):
|
||||||
|
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||||||
|
|
||||||
|
dst = ""
|
||||||
|
for i in xrange(len(src)):
|
||||||
|
temp1 = 0;
|
||||||
|
byteXorVal = 0;
|
||||||
|
for j in xrange(8):
|
||||||
|
temp1 ^= wkey[j]
|
||||||
|
sum2 = (sum2+j)*20021 + sum1
|
||||||
|
sum1 = (temp1*346)&0xFFFF
|
||||||
|
sum2 = (sum2+sum1)&0xFFFF
|
||||||
|
temp1 = (temp1*20021+1)&0xFFFF
|
||||||
|
byteXorVal ^= temp1 ^ sum2
|
||||||
|
curByte = ord(src[i])
|
||||||
|
if not decryption:
|
||||||
|
keyXorVal = curByte * 257;
|
||||||
|
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||||
|
if decryption:
|
||||||
|
keyXorVal = curByte * 257;
|
||||||
|
for j in xrange(8):
|
||||||
|
wkey[j] ^= keyXorVal;
|
||||||
|
dst+=chr(curByte)
|
||||||
|
return dst
|
||||||
|
|
||||||
|
def checksumPid(s):
|
||||||
|
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||||
|
crc = crc ^ (crc >> 16)
|
||||||
|
res = s
|
||||||
|
l = len(letters)
|
||||||
|
for i in (0,1):
|
||||||
|
b = crc & 0xff
|
||||||
|
pos = (b // l) ^ (b % l)
|
||||||
|
res += letters[pos%l]
|
||||||
|
crc >>= 8
|
||||||
|
return res
|
||||||
|
|
||||||
|
def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||||
|
def getSizeOfTrailingDataEntry(ptr, size):
|
||||||
|
bitpos, result = 0, 0
|
||||||
|
if size <= 0:
|
||||||
|
return result
|
||||||
|
while True:
|
||||||
|
v = ord(ptr[size-1])
|
||||||
|
result |= (v & 0x7F) << bitpos
|
||||||
|
bitpos += 7
|
||||||
|
size -= 1
|
||||||
|
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
|
||||||
|
return result
|
||||||
|
num = 0
|
||||||
|
flags >>= 1
|
||||||
|
while flags:
|
||||||
|
if flags & 1:
|
||||||
|
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
||||||
|
flags >>= 1
|
||||||
|
return num
|
||||||
|
|
||||||
|
|
||||||
|
class DrmStripper:
|
||||||
|
def loadSection(self, section):
|
||||||
|
if (section + 1 == self.num_sections):
|
||||||
|
endoff = len(self.data_file)
|
||||||
|
else:
|
||||||
|
endoff = self.sections[section + 1][0]
|
||||||
|
off = self.sections[section][0]
|
||||||
|
return self.data_file[off:endoff]
|
||||||
|
|
||||||
|
def patch(self, off, new):
|
||||||
|
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
|
||||||
|
|
||||||
|
def patchSection(self, section, new, in_off = 0):
|
||||||
|
if (section + 1 == self.num_sections):
|
||||||
|
endoff = len(self.data_file)
|
||||||
|
else:
|
||||||
|
endoff = self.sections[section + 1][0]
|
||||||
|
off = self.sections[section][0]
|
||||||
|
assert off + in_off + len(new) <= endoff
|
||||||
|
self.patch(off + in_off, new)
|
||||||
|
|
||||||
|
def parseDRM(self, data, count, pid):
|
||||||
|
pid = pid.ljust(16,'\0')
|
||||||
|
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
|
||||||
|
temp_key = PC1(keyvec1, pid, False)
|
||||||
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||||
|
found_key = None
|
||||||
|
for i in xrange(count):
|
||||||
|
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||||||
|
cookie = PC1(temp_key, cookie)
|
||||||
|
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
|
||||||
|
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
|
||||||
|
found_key = finalkey
|
||||||
|
break
|
||||||
|
return found_key
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, data_file, pid):
|
||||||
|
|
||||||
|
if checksumPid(pid[0:-2]) != pid:
|
||||||
|
raise DrmException("invalid PID checksum")
|
||||||
|
pid = pid[0:-2]
|
||||||
|
|
||||||
|
self.data_file = data_file
|
||||||
|
header = data_file[0:72]
|
||||||
|
if header[0x3C:0x3C+8] != 'BOOKMOBI':
|
||||||
|
raise DrmException("invalid file format")
|
||||||
|
self.num_sections, = struct.unpack('>H', data_file[76:78])
|
||||||
|
|
||||||
|
self.sections = []
|
||||||
|
for i in xrange(self.num_sections):
|
||||||
|
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
|
||||||
|
flags, val = a1, a2<<16|a3<<8|a4
|
||||||
|
self.sections.append( (offset, flags, val) )
|
||||||
|
|
||||||
|
sect = self.loadSection(0)
|
||||||
|
records, = struct.unpack('>H', sect[0x8:0x8+2])
|
||||||
|
mobi_length, = struct.unpack('>L',sect[0x14:0x18])
|
||||||
|
extra_data_flags = 0
|
||||||
|
if mobi_length >= 0xE4:
|
||||||
|
extra_data_flags, = struct.unpack('>L', sect[0xF0:0xF4])
|
||||||
|
|
||||||
|
|
||||||
|
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
||||||
|
if crypto_type != 2:
|
||||||
|
raise DrmException("invalid encryption type: %d" % crypto_type)
|
||||||
|
|
||||||
|
# calculate the keys
|
||||||
|
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
|
||||||
|
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
|
||||||
|
if not found_key:
|
||||||
|
raise DrmException("no key found. maybe the PID is incorrect")
|
||||||
|
|
||||||
|
# kill the drm keys
|
||||||
|
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
||||||
|
# kill the drm pointers
|
||||||
|
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
|
||||||
|
# clear the crypto type
|
||||||
|
self.patchSection(0, "\0" * 2, 0xC)
|
||||||
|
|
||||||
|
# decrypt sections
|
||||||
|
print "Decrypting. Please wait...",
|
||||||
|
for i in xrange(1, records+1):
|
||||||
|
data = self.loadSection(i)
|
||||||
|
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
|
||||||
|
self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
|
||||||
|
print "done"
|
||||||
|
def getResult(self):
|
||||||
|
return self.data_file
|
||||||
|
|
||||||
|
print "MobiDeDrm v0.04. Copyright (c) 2008 The Dark Reverser"
|
||||||
|
if len(sys.argv)<4:
|
||||||
|
print "Removes protection from Mobipocket books"
|
||||||
|
print "Usage:"
|
||||||
|
print " mobidedrm infile.mobi outfile.mobi PID"
|
||||||
|
else:
|
||||||
|
infile = sys.argv[1]
|
||||||
|
outfile = sys.argv[2]
|
||||||
|
pid = sys.argv[3]
|
||||||
|
data_file = file(infile, 'rb').read()
|
||||||
|
try:
|
||||||
|
file(outfile, 'wb').write(DrmStripper(data_file, pid).getResult())
|
||||||
|
except DrmException, e:
|
||||||
|
print "Error: %s" % e
|
Binary file not shown.
|
@ -0,0 +1,59 @@
|
||||||
|
on unlockfile(encryptedFile, MobiDeDRMPath, encryptionKey)
|
||||||
|
set encryptedFilePath to POSIX path of file encryptedFile
|
||||||
|
tell application "Finder" to Â
|
||||||
|
set parent_folder to (container of file encryptedFile) as text
|
||||||
|
tell application "Finder" to set fileName to (name of file encryptedFile) as text
|
||||||
|
set unlockedFilePath to POSIX path of file (parent_folder & "Unlocked_" & fileName)
|
||||||
|
set shellcommand to "python \"" & MobiDeDRMPath & "\" \"" & encryptedFilePath & "\" \"" & unlockedFilePath & "\" '" & encryptionKey & "'"
|
||||||
|
try
|
||||||
|
--with timeout of 5 seconds
|
||||||
|
-- display dialog "About to Unlock " & fileName buttons {"Unlock"} default button 1 giving up after 1
|
||||||
|
--end timeout
|
||||||
|
end try
|
||||||
|
set result to do shell script shellcommand
|
||||||
|
try
|
||||||
|
if (offset of "Error" in result) > 0 then
|
||||||
|
with timeout of 5 seconds
|
||||||
|
display dialog "Can't unlock file " & fileName & ".
|
||||||
|
|
||||||
|
" & result buttons ("OK") default button 1 giving up after 5
|
||||||
|
end timeout
|
||||||
|
end if
|
||||||
|
end try
|
||||||
|
end unlockfile
|
||||||
|
|
||||||
|
on unlockfolder(encryptedFolder, MobiDeDRMPath, encryptionKey)
|
||||||
|
tell application "Finder" to set encryptedFileList to (every file in folder encryptedFolder) whose (name extension is "prc") or (name extension is "mobi")
|
||||||
|
tell application "Finder" to set encryptedFolderList to (every folder in folder encryptedFolder)
|
||||||
|
repeat with this_item in encryptedFileList
|
||||||
|
unlockfile(this_item as text, MobiDeDRMPath, encryptionKey)
|
||||||
|
end repeat
|
||||||
|
repeat with this_item in encryptedFolderList
|
||||||
|
unlockfolder(this_item as text, MobiDeDRMPath, encryptionKey)
|
||||||
|
end repeat
|
||||||
|
end unlockfolder
|
||||||
|
|
||||||
|
on run
|
||||||
|
set MobiDeDRMPath to POSIX path of file ((path to me as text) & "Contents:Resources:MobiDeDRM.py")
|
||||||
|
set encryptedFolder to choose folder with prompt "Please choose the folder of encrypted Mobipocket files."
|
||||||
|
set encryptionKey to (display dialog "Enter Mobipocket key for encrypted Mobipocket files." default answer "X12QIL1M3D" buttons {"Cancel", "OK"} default button 2)
|
||||||
|
set encryptionKey to text returned of encryptionKey
|
||||||
|
unlockfolder(encryptedFolder, MobiDeDRMPath, encryptionKey)
|
||||||
|
end run
|
||||||
|
|
||||||
|
on open some_items
|
||||||
|
set MobiDeDRMPath to POSIX path of file ((path to me as text) & "Contents:Resources:MobiDeDRM.py")
|
||||||
|
set encryptionKey to (display dialog "Enter Mobipocket key for encrypted Mobipocket files." default answer "X12QIL1M3D" buttons {"Cancel", "OK"} default button 2)
|
||||||
|
set encryptionKey to text returned of encryptionKey
|
||||||
|
repeat with this_item in some_items
|
||||||
|
if (folder of (info for this_item) is true) then
|
||||||
|
unlockfolder(this_item as text, MobiDeDRMPath, encryptionKey)
|
||||||
|
else
|
||||||
|
tell application "Finder" to set item_extension to name extension of file this_item
|
||||||
|
if item_extension is "prc" or item_extension is "mobi" then
|
||||||
|
unlockfile(this_item as text, MobiDeDRMPath, encryptionKey)
|
||||||
|
end if
|
||||||
|
end if
|
||||||
|
end repeat
|
||||||
|
end open
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf330
|
||||||
|
{\fonttbl}
|
||||||
|
{\colortbl;\red255\green255\blue255;}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 362 B |
Loading…
Reference in New Issue