tools v2.1

combined kindle/mobi plugin
This commit is contained in:
Apprentice Alf 2010-10-26 18:18:46 +01:00
parent bf03edd18c
commit 5f0671db7f
110 changed files with 5872 additions and 7089 deletions

Binary file not shown.

View file

@ -1,682 +0,0 @@
#!/usr/bin/env python
#
# This is a WINDOWS python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# It can run standalone to convert K4PC files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
# K4PC files with DRM is no londer a multi-step process.
#
# ***NOTE*** Calibre and K4PC must be installed on the same windows machine
# for the plugin version to function properly.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file and import that ZIP into Calibre
# using its plugin configuration GUI.
#
# Thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump from
# which this script steals most unashamedly.
#
# Changelog
# 0.01 - Initial version - Utilizes skindle and CMBDTC method of obtaining
# book specific pids from K4PC books. If Calibre and K4PC are installed
# on the same windows machine, Calibre plugin functionality is once
# again restored.
"""
Comprehensive Mazama Book DRM with Topaz Cryptography V2.0
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
y2/pHuYme7U1TsgSjwIDAQAB
-----END PUBLIC KEY-----
"""
from __future__ import with_statement
import csv
import sys
import os
import getopt
import zlib
import binascii
from struct import pack
from struct import unpack
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
string_at, Structure, c_void_p, cast
import _winreg as winreg
import traceback
import hashlib
__version__ = '0.01'
global kindleDatabase
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class DrmException(Exception):
pass
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
def GetSystemDirectory():
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
GetSystemDirectoryW.restype = c_uint
def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1)
GetSystemDirectoryW(buffer, len(buffer))
return buffer.value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory()
def GetVolumeSerialNumber():
GetVolumeInformationW = kernel32.GetVolumeInformationW
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
POINTER(c_uint), POINTER(c_uint),
POINTER(c_uint), c_wchar_p, c_uint]
GetVolumeInformationW.restype = c_uint
def GetVolumeSerialNumber(path):
vsn = c_uint(0)
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
return vsn.value
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetUserName():
GetUserNameW = advapi32.GetUserNameW
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
GetUserNameW.restype = c_uint
def GetUserName():
buffer = create_unicode_buffer(32)
size = c_uint(len(buffer))
while not GetUserNameW(buffer, byref(size)):
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
return buffer.value.encode('utf-16-le')[::2]
return GetUserName
GetUserName = GetUserName()
def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)):
raise DrmException("Failed to Unprotect Data")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
#
# Returns the MD5 digest of "message"
#
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
#
# Returns the MD5 digest of "message"
#
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
#
# Locate and open the Kindle.info file.
#
def openKindleInfo():
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
#
# Parse the Kindle.info file and return the records as a list of key-values
#
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
#
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
#
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return name
#
# Print all the records from the kindle.info file.
#
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------\n")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
#
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
return CryptUnprotectData(encryptedValue,"")
#
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
#
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
#
# 8 bits to six bits encoding from hash to generate PID string
#
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
#
# Hash the bytes in data and then encode the digest with the characters in map
#
def encodeHash(data,map):
return encode(MD5(data),map)
#
# Encode the bytes in data with the characters in map
#
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
#
# Decode the string in data with the characters in map. Returns the decoded bytes
#
def decode(data,map):
result = ""
for i in range (0,len(data),2):
high = map.find(data[i])
low = map.find(data[i+1])
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
#
# Encryption table used to generate the device PID
#
def generatePidEncryptionTable() :
table = []
for counter1 in range (0,0x100):
value = counter1
for counter2 in range (0,8):
if (value & 1 == 0) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table.append(value)
return table
#
# Seed value used to generate the device PID
#
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
#
# Generate the device PID
#
def generateDevicePID(table,dsn,nbRoll):
seed = generatePidSeed(table,dsn)
pidAscii = ""
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
pid[index] = pid[index] ^ ord(dsn[counter])
index = (index+1) %8
for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index]
return pidAscii
#
# Returns two bit at offset from a bit field
#
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
#
# Returns the six bits at offset from a bit field
#
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
#
# MobiDeDrm-0.16 Stuff
#
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
# 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 getSizeOfTrailingDataEntries(ptr, size, flags):
def getSizeOfTrailingDataEntry(ptr, size):
bitpos, result = 0, 0
if size <= 0:
return result
while True:
v = ord(ptr[size-1])
result |= (v & 0x7F) << bitpos
bitpos += 7
size -= 1
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
return result
num = 0
testflags = flags >> 1
while testflags:
if testflags & 1:
num += getSizeOfTrailingDataEntry(ptr, size - num)
testflags >>= 1
# Multibyte data, if present, is included in the encryption, so
# we do not need to check the low bit.
# if flags & 1:
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num
#
# This class does all the heavy lifting.
#
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 = unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = unpack('>LL16sLL', cookie)
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
found_key = finalkey
break
if not found_key:
# Then try the default encoding that doesn't require a PID
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
verification, size, type, cksum, cookie = unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = unpack('>LL16sLL', cookie)
if verification == ver and cksum == temp_key_sum:
found_key = finalkey
break
return found_key
def __init__(self, data_file):
self.data_file = data_file
header = data_file[0:72]
if header[0x3C:0x3C+8] != 'BOOKMOBI':
raise DrmException("invalid file format")
self.num_sections, = unpack('>H', data_file[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = 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, = unpack('>H', sect[0x8:0x8+2])
mobi_length, = unpack('>L',sect[0x14:0x18])
mobi_version, = unpack('>L',sect[0x68:0x6C])
extra_data_flags = 0
print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
if (mobi_length >= 0xE4) and (mobi_version >= 5):
extra_data_flags, = unpack('>H', sect[0xF2:0xF4])
print "Extra Data Flags = %d" %extra_data_flags
crypto_type, = unpack('>H', sect[0xC:0xC+2])
if crypto_type == 0:
print "This book is not encrypted."
else:
if crypto_type == 1:
raise DrmException("cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)
# determine the EXTH Offset.
exth_off = unpack('>I', sect[20:24])[0] + 16 + self.sections[0][0]
# Grab the entire EXTH block and feed it to the getK4PCPids function.
exth = data_file[exth_off:self.sections[0+1][0]]
pid = getK4PCPids(exth)
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = unpack('>LLLL', sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("no PIDs found in this file")
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 "\nDecrypting. Please wait . . .",
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
if i%100 == 0:
print ".",
# print "record %d, extra_size %d" %(i,extra_size)
new_data += PC1(found_key, data[0:len(data) - extra_size])
if extra_size > 0:
new_data += data[-extra_size:]
#self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data
print "done!"
print "\nPlease only use your new-found powers for good."
def getResult(self):
return self.data_file
#
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid.
#
def getK4PCPids(exth):
global kindleDatabase
try:
kindleDatabase = parseKindleInfo()
except Exception as message:
print(message)
if kindleDatabase != None :
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
# Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode to calculate the DSN
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
print("\nDSN: " + DSN)
# Compute the device PID (for which I can tell, is used for nothing).
# But hey, stuff being printed out is apparently cool.
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
print("Device PID: " + devicePID)
# Compute book PID
exth_records = {}
nitems, = unpack('>I', exth[8:12])
pos = 12
# Parse the EXTH records, storing data indexed by type
for i in xrange(nitems):
type, size = unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
exth_records[type] = content
pos += size
# Grab the contents of the type 209 exth record
if exth_records[209] != None:
data = exth_records[209]
else:
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4PC file?")
# Parse the 209 data to find the the exth record with the token data.
# The last character of the 209 data points to the record with the token.
# Always 208 from my experience, but I'll leave the logic in case that changes.
for i in xrange(len(data)):
if ord(data[i]) != 0:
if exth_records[ord(data[i])] != None:
token = exth_records[ord(data[i])]
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
print("Account Token: " + kindleAccountToken)
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
bookPID = encodePID(pidHash)
if exth_records[503] != None:
print "Pid for " + exth_records[503] + ": " + bookPID
else:
print ("Book PID: " + bookPID )
return bookPID
raise DrmException("\nCould not access K4PC data - Perhaps K4PC is not installed/configured?")
return null
if not __name__ == "__main__":
from calibre.customize import FileTypePlugin
class K4PCDeDRM(FileTypePlugin):
name = 'K4PCDeDRM' # Name of the plugin
description = 'Removes DRM from K4PC files'
supported_platforms = ['windows'] # Platforms this plugin will run on
author = 'DiapDealer' # The author of this plugin
version = (0, 0, 1) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
def run(self, path_to_ebook):
from calibre.gui2 import is_ok_to_use_qt
from PyQt4.Qt import QMessageBox
data_file = file(path_to_ebook, 'rb').read()
try:
unlocked_file = DrmStripper(data_file).getResult()
except DrmException:
# ignore the error
pass
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "K4PCDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
d.show()
d.raise_()
d.exec_()
return path_to_ebook
#def customization_help(self, gui=False):
# return 'Enter PID (separate multiple PIDs with comma)'
if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout)
print ('K4PCDeDrm v%(__version__)s '
'provided DiapDealer.' % globals())
if len(sys.argv)<3:
print "Removes DRM protection from K4PC books"
print "Usage:"
print " %s <infile> <outfile>" % sys.argv[0]
sys.exit(1)
else:
infile = sys.argv[1]
outfile = sys.argv[2]
data_file = file(infile, 'rb').read()
try:
strippedFile = DrmStripper(data_file)
file(outfile, 'wb').write(strippedFile.getResult())
except DrmException, e:
print "Error: %s" % e
sys.exit(1)
sys.exit(0)

Binary file not shown.

View file

@ -0,0 +1,23 @@
Plugin for K4PC, K4Mac and Mobi Books
Will work on Linux (standard DRM Mobi books only), Mac OS X (standard DRM Mobi books and "Kindle for Mac" books, and Windows (standard DRM Mobi books and "Kindle for PC" books.
This plugin supersedes MobiDeDRM, K4DeDRM, and K4PCDeDRM plugins. If you install this plugin, those plugins can be safely removed.
This plugin is meant to convert "Kindle for PC", "Kindle for Mac" and "Mobi" ebooks with DRM to unlocked Mobi files. Calibre can then convert them to whatever format you desire. It is meant to function without having to install any dependencies except for Calibre being on your same machine and in the same account as your "Kindle for PC" or "Kindle for Mac" application if you are going to remove the DRM from those types of books.
Installation:
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (k4mobidedrm_vXX_plugin.zip) and click the 'Add' button. You're done.
Configuration:
Highlight the plugin (K4MobiDeDRM under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter a comma separated list of your 10 digit PIDs. This is not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books.
Troubleshooting:
If you find that it's not working for you (imported azw's are not converted to mobi format), you can save a lot of time and trouble by trying to add the azw file to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.azw". Don't type the quotes and obviously change the 'your_ebook.azw' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

View file

@ -1,12 +0,0 @@
K4PCDeDRM - K4PCDeDRM_X.XX_plugin.zip
Requires Calibre version 0.6.44 or higher.
This work is based on the work of cmbtc, skindle, mobidedrm. and skindleAll I had the much easier job of converting them to a Calibre plugin.
This plugin is meant to Kindle for PC azw ebooks that are protected
with Amazon's Mobi based encryption. It is meant to function without having to install any dependencies... other than having both Calibre installed and Kindle for PC on the same machine, of course.
Installation:
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (K4PCDeDRM_X.XX_plugin.zip) and click the 'Add' button. you're done.

View file

@ -0,0 +1,21 @@
eReader PDB2PML - eReaderPDB2PML_vXX_plugin.zip
All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin.
This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
Installation:
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done.
Configuration:
Highlight the plugin (eReader PDB 2 PML under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your name and last 8 digits of the credit card number separated by a comma: Your Name,12341234
If you've purchased books with more than one credit card, separate the info with a colon: Your Name,12341234:Other Name,23452345 (NOTE: Do NOT put quotes around your name like you do with the original script!!)
Troubleshooting:
If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

View file

@ -1,13 +0,0 @@
MobiDeDRM - MobiDeDRM_X.XX_plugin.zip
Requires Calibre version 0.6.44 or higher.
This work is based on the current mobidedrm.py code.
This plugin is meant to Mobipocket and Kindle ebooks that are protected
with Amazon's Mobi based encryption. It is meant to function without having to install any dependencies... other than having both Calibre installed. You must know the PID orf the device you are using or the book specific PID to use this plugin.
Installation:
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (MobiDeDRM_X.XX_plugin.zip) and click the 'Add' button.
Then enter your PIDS in the plugin customization window separated by commas (with no spaces).

Binary file not shown.

View file

@ -0,0 +1,21 @@
eReader PDB2PML - eReaderPDB2PML_vXX_plugin.zip
All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin.
This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
Installation:
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done.
Configuration:
Highlight the plugin (eReader PDB 2 PML under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your name and last 8 digits of the credit card number separated by a comma: Your Name,12341234
If you've purchased books with more than one credit card, separate the info with a colon: Your Name,12341234:Other Name,23452345 (NOTE: Do NOT put quotes around your name like you do with the original script!!)
Troubleshooting:
If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
as well get used to it. ;)
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.

View file

@ -0,0 +1,148 @@
#!/usr/bin/env python
# eReaderPDB2PML_v01_plugin.py
# Released under the terms of the GNU General Public Licence, version 3 or
# later. <http://www.gnu.org/licenses/>
#
# All credit given to The Dark Reverser for the original standalone script.
# I had the much easier job of converting it to Calibre a plugin.
#
# This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files.
# Calibre can then convert it to whatever format you desire.
# It is meant to function without having to install any dependencies...
# other than having Calibre installed, of course. I've included the psyco libraries
# (compiled for each platform) for speed. If your system can use them, great!
# Otherwise, they won't be used and things will just work slower.
#
# Installation:
# Go to Calibre's Preferences page... click on the Plugins button. Use the file
# dialog button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and
# click the 'Add' button. You're done.
#
# Configuration:
# Highlight the plugin (eReader PDB 2 PML) and click the
# "Customize Plugin" button on Calibre's Preferences->Plugins page.
# Enter your name and the last 8 digits of the credit card number separated by
# a comma: Your Name,12341234
#
# If you've purchased books with more than one credit card, separate the info with
# a colon: Your Name,12341234:Other Name,23452345
# NOTE: Do NOT put quotes around your name like you do with the original script!!
#
# Revision history:
# 0.1 - Initial release
import sys, os
from calibre.customize import FileTypePlugin
class eRdrDeDRM(FileTypePlugin):
name = 'eReader PDB 2 PML' # Name of the plugin
description = 'Removes DRM from secure pdb files. \
Credit given to The Dark Reverser for the original standalone script.'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'DiapDealer' # The author of this plugin
version = (0, 0, 1) # The version number of this plugin
file_types = set(['pdb']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
def run(self, path_to_ebook):
from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.constants import iswindows, isosx
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
ppath = os.path.join(self.sys_insertion_path, pdir)
sys.path.insert(0, ppath)
#sys.path.append(ppath)
global bookname, erdr2pml
import erdr2pml
if 'psyco' in sys.modules:
print 'Using psyco acceleration for %s.' % pdir
else:
print 'NOT using psyco acceleration for %s. Conversion may be slow.' % pdir
infile = path_to_ebook
bookname = os.path.splitext(os.path.basename(infile))[0]
outdir = PersistentTemporaryDirectory()
pmlzfile = self.temporary_file(bookname + '.pmlz')
if self.site_customization:
keydata = self.site_customization
ar = keydata.split(':')
for i in ar:
try:
name, cc = i.split(',')
except ValueError:
sys.path.remove(ppath)
print ' Error parsing user supplied data.'
return path_to_ebook
try:
print "Processing..."
import time
start_time = time.time()
pmlfilepath = self.convertEreaderToPml(infile, name, cc, outdir)
if pmlfilepath and pmlfilepath != 1:
import zipfile
import shutil
print " Creating PMLZ file"
myZipFile = zipfile.ZipFile(pmlzfile.name,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir)
for file in list:
localname = file
filePath = os.path.join(outdir,file)
if os.path.isfile(filePath):
myZipFile.write(filePath, localname)
elif os.path.isdir(filePath):
imageList = os.listdir(filePath)
localimgdir = os.path.basename(filePath)
for image in imageList:
localname = os.path.join(localimgdir,image)
imagePath = os.path.join(filePath,image)
if os.path.isfile(imagePath):
myZipFile.write(imagePath, localname)
myZipFile.close()
end_time = time.time()
search_time = end_time - start_time
print 'elapsed time: %.2f seconds' % (search_time, )
print "done"
return pmlzfile.name
else:
raise ValueError('Error Creating PML file.')
except ValueError, e:
print "Error: %s" % e
pass
raise Exception('Couldn\'t decrypt pdb file.')
else:
raise Exception('No name and CC# provided.')
def convertEreaderToPml(self, infile, name, cc, outdir):
print " Decoding File"
sect = erdr2pml.Sectionizer(infile, 'PNRdPPrs')
er = erdr2pml.EreaderProcessor(sect.loadSection, name, cc)
if er.getNumImages() > 0:
print " Extracting images"
#imagedir = bookname + '_img/'
imagedir = 'images/'
imagedirpath = os.path.join(outdir,imagedir)
if not os.path.exists(imagedirpath):
os.makedirs(imagedirpath)
for i in xrange(er.getNumImages()):
name, contents = er.getImage(i)
file(os.path.join(imagedirpath, name), 'wb').write(contents)
print " Extracting pml"
pml_string = er.getText()
pmlfilename = bookname + ".pml"
try:
file(os.path.join(outdir, pmlfilename),'wb').write(erdr2pml.cleanPML(pml_string))
return os.path.join(outdir, pmlfilename)
except:
return 1
def customization_help(self, gui=False):
return 'Enter Account Name & Last 8 digits of Credit Card number (separate with a comma)'

View file

@ -0,0 +1,47 @@
K 25
svn:wc:ra_dav:version-url
V 41
/svn/!svn/ver/70200/psyco/dist/py-support
END
core.py
K 25
svn:wc:ra_dav:version-url
V 49
/svn/!svn/ver/70200/psyco/dist/py-support/core.py
END
support.py
K 25
svn:wc:ra_dav:version-url
V 52
/svn/!svn/ver/49315/psyco/dist/py-support/support.py
END
classes.py
K 25
svn:wc:ra_dav:version-url
V 52
/svn/!svn/ver/35003/psyco/dist/py-support/classes.py
END
__init__.py
K 25
svn:wc:ra_dav:version-url
V 53
/svn/!svn/ver/35003/psyco/dist/py-support/__init__.py
END
logger.py
K 25
svn:wc:ra_dav:version-url
V 51
/svn/!svn/ver/23284/psyco/dist/py-support/logger.py
END
kdictproxy.py
K 25
svn:wc:ra_dav:version-url
V 55
/svn/!svn/ver/35003/psyco/dist/py-support/kdictproxy.py
END
profiler.py
K 25
svn:wc:ra_dav:version-url
V 53
/svn/!svn/ver/70200/psyco/dist/py-support/profiler.py
END

View file

@ -0,0 +1,7 @@
K 10
svn:ignore
V 14
*~
*.pyc
*.pyo
END

View file

@ -0,0 +1,266 @@
10
dir
78269
http://codespeak.net/svn/psyco/dist/py-support
http://codespeak.net/svn
2009-12-18T16:35:35.119276Z
70200
arigo
has-props
fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
core.py
file
2010-10-25T15:10:42.000000Z
3b362177a839893c9e867880b3a7cef3
2009-12-18T16:35:35.119276Z
70200
arigo
has-props
8144
support.py
file
2010-10-25T15:10:42.000000Z
b0551e975d774f2f7f58a29ed4b6b90e
2007-12-03T12:27:25.632574Z
49315
arigo
has-props
6043
classes.py
file
2010-10-25T15:10:42.000000Z
5932ed955198d16ec17285dfb195d341
2006-11-26T13:03:26.949973Z
35003
arigo
has-props
1440
__init__.py
file
2010-10-25T15:10:42.000000Z
219582b5182dfa38a9119d059a71965f
2006-11-26T13:03:26.949973Z
35003
arigo
has-props
1895
logger.py
file
2010-10-25T15:10:42.000000Z
aa21f905df036af43082e1ea2a2561ee
2006-02-13T15:02:51.744168Z
23284
arigo
has-props
2678
kdictproxy.py
file
2010-10-25T15:10:42.000000Z
1c8611748dcee5b29848bf25be3ec473
2006-11-26T13:03:26.949973Z
35003
arigo
has-props
4369
profiler.py
file
2010-10-25T15:10:42.000000Z
858162366cbc39cd9e249e35e6f510c4
2009-12-18T16:35:35.119276Z
70200
arigo
has-props
11238

View file

@ -0,0 +1,9 @@
K 13
svn:eol-style
V 6
native
K 12
svn:keywords
V 23
Author Date Id Revision
END

View file

@ -0,0 +1,9 @@
K 13
svn:eol-style
V 6
native
K 12
svn:keywords
V 23
Author Date Id Revision
END

View file

@ -0,0 +1,9 @@
K 13
svn:eol-style
V 6
native
K 12
svn:keywords
V 23
Author Date Id Revision
END

View file

@ -0,0 +1,9 @@
K 13
svn:eol-style
V 6
native
K 12
svn:keywords
V 23
Author Date Id Revision
END

View file

@ -0,0 +1,9 @@
K 13
svn:eol-style
V 6
native
K 12
svn:keywords
V 23
Author Date Id Revision
END

View file

@ -0,0 +1,9 @@
K 13
svn:eol-style
V 6
native
K 12
svn:keywords
V 23
Author Date Id Revision
END

View file

@ -0,0 +1,9 @@
K 13
svn:eol-style
V 6
native
K 12
svn:keywords
V 23
Author Date Id Revision
END

View file

@ -0,0 +1,54 @@
###########################################################################
#
# Psyco top-level file of the Psyco package.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco -- the Python Specializing Compiler.
Typical usage: add the following lines to your application's main module,
preferably after the other imports:
try:
import psyco
psyco.full()
except ImportError:
print 'Psyco not installed, the program will just run slower'
"""
###########################################################################
#
# This module is present to make 'psyco' a package and to
# publish the main functions and variables.
#
# More documentation can be found in core.py.
#
# Try to import the dynamic-loading _psyco and report errors
try:
import _psyco
except ImportError, e:
extramsg = ''
import sys, imp
try:
file, filename, (suffix, mode, type) = imp.find_module('_psyco', __path__)
except ImportError:
ext = [suffix for suffix, mode, type in imp.get_suffixes()
if type == imp.C_EXTENSION]
if ext:
extramsg = (" (cannot locate the compiled extension '_psyco%s' "
"in the package path '%s')" % (ext[0], '; '.join(__path__)))
else:
extramsg = (" (check that the compiled extension '%s' is for "
"the correct Python version; this is Python %s)" %
(filename, sys.version.split()[0]))
raise ImportError, str(e) + extramsg
# Publish important data by importing them in the package
from support import __version__, error, warning, _getrealframe, _getemulframe
from support import version_info, __version__ as hexversion
from core import full, profile, background, runonly, stop, cannotcompile
from core import log, bind, unbind, proxy, unproxy, dumpcodebuf
from _psyco import setfilter
from _psyco import compact, compacttype

View file

@ -0,0 +1,42 @@
###########################################################################
#
# Psyco class support module.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco class support module.
'psyco.classes.psyobj' is an alternate Psyco-optimized root for classes.
Any class inheriting from it or using the metaclass '__metaclass__' might
get optimized specifically for Psyco. It is equivalent to call
psyco.bind() on the class object after its creation.
Importing everything from psyco.classes in a module will import the
'__metaclass__' name, so all classes defined after a
from psyco.classes import *
will automatically use the Psyco-optimized metaclass.
"""
###########################################################################
__all__ = ['psyobj', 'psymetaclass', '__metaclass__']
from _psyco import compacttype
import core
from types import FunctionType
class psymetaclass(compacttype):
"Psyco-optimized meta-class. Turns all methods into Psyco proxies."
def __new__(cls, name, bases, dict):
bindlist = dict.get('__psyco__bind__')
if bindlist is None:
bindlist = [key for key, value in dict.items()
if isinstance(value, FunctionType)]
for attr in bindlist:
dict[attr] = core.proxy(dict[attr])
return super(psymetaclass, cls).__new__(cls, name, bases, dict)
psyobj = psymetaclass("psyobj", (), {})
__metaclass__ = psymetaclass

View file

@ -0,0 +1,231 @@
###########################################################################
#
# Psyco main functions.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco main functions.
Here are the routines that you can use from your applications.
These are mostly interfaces to the C core, but they depend on
the Python version.
You can use these functions from the 'psyco' module instead of
'psyco.core', e.g.
import psyco
psyco.log('/tmp/psyco.log')
psyco.profile()
"""
###########################################################################
import _psyco
import types
from support import *
newfunction = types.FunctionType
newinstancemethod = types.MethodType
# Default charge profiler values
default_watermark = 0.09 # between 0.0 (0%) and 1.0 (100%)
default_halflife = 0.5 # seconds
default_pollfreq_profile = 20 # Hz
default_pollfreq_background = 100 # Hz -- a maximum for sleep's resolution
default_parentframe = 0.25 # should not be more than 0.5 (50%)
def full(memory=None, time=None, memorymax=None, timemax=None):
"""Compile as much as possible.
Typical use is for small scripts performing intensive computations
or string handling."""
import profiler
p = profiler.FullCompiler()
p.run(memory, time, memorymax, timemax)
def profile(watermark = default_watermark,
halflife = default_halflife,
pollfreq = default_pollfreq_profile,
parentframe = default_parentframe,
memory=None, time=None, memorymax=None, timemax=None):
"""Turn on profiling.
The 'watermark' parameter controls how easily running functions will
be compiled. The smaller the value, the more functions are compiled."""
import profiler
p = profiler.ActivePassiveProfiler(watermark, halflife,
pollfreq, parentframe)
p.run(memory, time, memorymax, timemax)
def background(watermark = default_watermark,
halflife = default_halflife,
pollfreq = default_pollfreq_background,
parentframe = default_parentframe,
memory=None, time=None, memorymax=None, timemax=None):
"""Turn on passive profiling.
This is a very lightweight mode in which only intensively computing
functions can be detected. The smaller the 'watermark', the more functions
are compiled."""
import profiler
p = profiler.PassiveProfiler(watermark, halflife, pollfreq, parentframe)
p.run(memory, time, memorymax, timemax)
def runonly(memory=None, time=None, memorymax=None, timemax=None):
"""Nonprofiler.
XXX check if this is useful and document."""
import profiler
p = profiler.RunOnly()
p.run(memory, time, memorymax, timemax)
def stop():
"""Turn off all automatic compilation. bind() calls remain in effect."""
import profiler
profiler.go([])
def log(logfile='', mode='w', top=10):
"""Enable logging to the given file.
If the file name is unspecified, a default name is built by appending
a 'log-psyco' extension to the main script name.
Mode is 'a' to append to a possibly existing file or 'w' to overwrite
an existing file. Note that the log file may grow quickly in 'a' mode."""
import profiler, logger
if not logfile:
import os
logfile, dummy = os.path.splitext(sys.argv[0])
if os.path.basename(logfile):
logfile += '.'
logfile += 'log-psyco'
if hasattr(_psyco, 'VERBOSE_LEVEL'):
print >> sys.stderr, 'psyco: logging to', logfile
# logger.current should be a real file object; subtle problems
# will show up if its write() and flush() methods are written
# in Python, as Psyco will invoke them while compiling.
logger.current = open(logfile, mode)
logger.print_charges = top
profiler.logger = logger
logger.writedate('Logging started')
cannotcompile(logger.psycowrite)
_psyco.statwrite(logger=logger.psycowrite)
def bind(x, rec=None):
"""Enable compilation of the given function, method, or class object.
If C is a class (or anything with a '__dict__' attribute), bind(C) will
rebind all functions and methods found in C.__dict__ (which means, for
classes, all methods defined in the class but not in its parents).
The optional second argument specifies the number of recursive
compilation levels: all functions called by func are compiled
up to the given depth of indirection."""
if isinstance(x, types.MethodType):
x = x.im_func
if isinstance(x, types.FunctionType):
if rec is None:
x.func_code = _psyco.proxycode(x)
else:
x.func_code = _psyco.proxycode(x, rec)
return
if hasattr(x, '__dict__'):
funcs = [o for o in x.__dict__.values()
if isinstance(o, types.MethodType)
or isinstance(o, types.FunctionType)]
if not funcs:
raise error, ("nothing bindable found in %s object" %
type(x).__name__)
for o in funcs:
bind(o, rec)
return
raise TypeError, "cannot bind %s objects" % type(x).__name__
def unbind(x):
"""Reverse of bind()."""
if isinstance(x, types.MethodType):
x = x.im_func
if isinstance(x, types.FunctionType):
try:
f = _psyco.unproxycode(x.func_code)
except error:
pass
else:
x.func_code = f.func_code
return
if hasattr(x, '__dict__'):
for o in x.__dict__.values():
if (isinstance(o, types.MethodType)
or isinstance(o, types.FunctionType)):
unbind(o)
return
raise TypeError, "cannot unbind %s objects" % type(x).__name__
def proxy(x, rec=None):
"""Return a Psyco-enabled copy of the function.
The original function is still available for non-compiled calls.
The optional second argument specifies the number of recursive
compilation levels: all functions called by func are compiled
up to the given depth of indirection."""
if isinstance(x, types.FunctionType):
if rec is None:
code = _psyco.proxycode(x)
else:
code = _psyco.proxycode(x, rec)
return newfunction(code, x.func_globals, x.func_name)
if isinstance(x, types.MethodType):
p = proxy(x.im_func, rec)
return newinstancemethod(p, x.im_self, x.im_class)
raise TypeError, "cannot proxy %s objects" % type(x).__name__
def unproxy(proxy):
"""Return a new copy of the original function of method behind a proxy.
The result behaves like the original function in that calling it
does not trigger compilation nor execution of any compiled code."""
if isinstance(proxy, types.FunctionType):
return _psyco.unproxycode(proxy.func_code)
if isinstance(proxy, types.MethodType):
f = unproxy(proxy.im_func)
return newinstancemethod(f, proxy.im_self, proxy.im_class)
raise TypeError, "%s objects cannot be proxies" % type(proxy).__name__
def cannotcompile(x):
"""Instruct Psyco never to compile the given function, method
or code object."""
if isinstance(x, types.MethodType):
x = x.im_func
if isinstance(x, types.FunctionType):
x = x.func_code
if isinstance(x, types.CodeType):
_psyco.cannotcompile(x)
else:
raise TypeError, "unexpected %s object" % type(x).__name__
def dumpcodebuf():
"""Write in file psyco.dump a copy of the emitted machine code,
provided Psyco was compiled with a non-zero CODE_DUMP.
See py-utils/httpxam.py to examine psyco.dump."""
if hasattr(_psyco, 'dumpcodebuf'):
_psyco.dumpcodebuf()
###########################################################################
# Psyco variables
# error * the error raised by Psyco
# warning * the warning raised by Psyco
# __in_psyco__ * a new built-in variable which is always zero, but which
# Psyco special-cases by returning 1 instead. So
# __in_psyco__ can be used in a function to know if
# that function is being executed by Psyco or not.

View file

@ -0,0 +1,133 @@
###########################################################################
#
# Support code for the 'psyco.compact' type.
from __future__ import generators
try:
from UserDict import DictMixin
except ImportError:
# backported from Python 2.3 to Python 2.2
class DictMixin:
# Mixin defining all dictionary methods for classes that already have
# a minimum dictionary interface including getitem, setitem, delitem,
# and keys. Without knowledge of the subclass constructor, the mixin
# does not define __init__() or copy(). In addition to the four base
# methods, progressively more efficiency comes with defining
# __contains__(), __iter__(), and iteritems().
# second level definitions support higher levels
def __iter__(self):
for k in self.keys():
yield k
def has_key(self, key):
try:
value = self[key]
except KeyError:
return False
return True
def __contains__(self, key):
return self.has_key(key)
# third level takes advantage of second level definitions
def iteritems(self):
for k in self:
yield (k, self[k])
def iterkeys(self):
return self.__iter__()
# fourth level uses definitions from lower levels
def itervalues(self):
for _, v in self.iteritems():
yield v
def values(self):
return [v for _, v in self.iteritems()]
def items(self):
return list(self.iteritems())
def clear(self):
for key in self.keys():
del self[key]
def setdefault(self, key, default):
try:
return self[key]
except KeyError:
self[key] = default
return default
def pop(self, key, *args):
if len(args) > 1:
raise TypeError, "pop expected at most 2 arguments, got "\
+ repr(1 + len(args))
try:
value = self[key]
except KeyError:
if args:
return args[0]
raise
del self[key]
return value
def popitem(self):
try:
k, v = self.iteritems().next()
except StopIteration:
raise KeyError, 'container is empty'
del self[k]
return (k, v)
def update(self, other):
# Make progressively weaker assumptions about "other"
if hasattr(other, 'iteritems'): # iteritems saves memory and lookups
for k, v in other.iteritems():
self[k] = v
elif hasattr(other, '__iter__'): # iter saves memory
for k in other:
self[k] = other[k]
else:
for k in other.keys():
self[k] = other[k]
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __repr__(self):
return repr(dict(self.iteritems()))
def __cmp__(self, other):
if other is None:
return 1
if isinstance(other, DictMixin):
other = dict(other.iteritems())
return cmp(dict(self.iteritems()), other)
def __len__(self):
return len(self.keys())
###########################################################################
from _psyco import compact
class compactdictproxy(DictMixin):
def __init__(self, ko):
self._ko = ko # compact object of which 'self' is the dict
def __getitem__(self, key):
return compact.__getslot__(self._ko, key)
def __setitem__(self, key, value):
compact.__setslot__(self._ko, key, value)
def __delitem__(self, key):
compact.__delslot__(self._ko, key)
def keys(self):
return compact.__members__.__get__(self._ko)
def clear(self):
keys = self.keys()
keys.reverse()
for key in keys:
del self[key]
def __repr__(self):
keys = ', '.join(self.keys())
return '<compactdictproxy object {%s}>' % (keys,)

View file

@ -0,0 +1,96 @@
###########################################################################
#
# Psyco logger.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco logger.
See log() in core.py.
"""
###########################################################################
import _psyco
from time import time, localtime, strftime
current = None
print_charges = 10
dump_delay = 0.2
dump_last = 0.0
def write(s, level):
t = time()
f = t-int(t)
try:
current.write("%s.%02d %-*s%s\n" % (
strftime("%X", localtime(int(t))),
int(f*100.0), 63-level, s,
"%"*level))
current.flush()
except (OSError, IOError):
pass
def psycowrite(s):
t = time()
f = t-int(t)
try:
current.write("%s.%02d %-*s%s\n" % (
strftime("%X", localtime(int(t))),
int(f*100.0), 60, s.strip(),
"% %"))
current.flush()
except (OSError, IOError):
pass
##def writelines(lines, level=0):
## if lines:
## t = time()
## f = t-int(t)
## timedesc = strftime("%x %X", localtime(int(t)))
## print >> current, "%s.%03d %-*s %s" % (
## timedesc, int(f*1000),
## 50-level, lines[0],
## "+"*level)
## timedesc = " " * (len(timedesc)+5)
## for line in lines[1:]:
## print >> current, timedesc, line
def writememory():
write("memory usage: %d+ kb" % _psyco.memory(), 1)
def dumpcharges():
global dump_last
if print_charges:
t = time()
if not (dump_last <= t < dump_last+dump_delay):
if t <= dump_last+1.5*dump_delay:
dump_last += dump_delay
else:
dump_last = t
#write("%s: charges:" % who, 0)
lst = _psyco.stattop(print_charges)
if lst:
f = t-int(t)
lines = ["%s.%02d ______\n" % (
strftime("%X", localtime(int(t))),
int(f*100.0))]
i = 1
for co, charge in lst:
detail = co.co_filename
if len(detail) > 19:
detail = '...' + detail[-17:]
lines.append(" #%-3d |%4.1f %%| %-26s%20s:%d\n" %
(i, charge*100.0, co.co_name, detail,
co.co_firstlineno))
i += 1
current.writelines(lines)
current.flush()
def writefinalstats():
dumpcharges()
writememory()
writedate("program exit")
def writedate(msg):
write('%s, %s' % (msg, strftime("%x")), 20)

View file

@ -0,0 +1,379 @@
###########################################################################
#
# Psyco profiler (Python part).
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco profiler (Python part).
The implementation of the non-time-critical parts of the profiler.
See profile() and full() in core.py for the easy interface.
"""
###########################################################################
import _psyco
from support import *
import math, time, types, atexit
now = time.time
try:
import thread
except ImportError:
import dummy_thread as thread
# current profiler instance
current = None
# enabled profilers, in order of priority
profilers = []
# logger module (when enabled by core.log())
logger = None
# a lock for a thread-safe go()
go_lock = thread.allocate_lock()
def go(stop=0):
# run the highest-priority profiler in 'profilers'
global current
go_lock.acquire()
try:
prev = current
if stop:
del profilers[:]
if prev:
if profilers and profilers[0] is prev:
return # best profiler already running
prev.stop()
current = None
for p in profilers[:]:
if p.start():
current = p
if logger: # and p is not prev:
logger.write("%s: starting" % p.__class__.__name__, 5)
return
finally:
go_lock.release()
# no profiler is running now
if stop:
if logger:
logger.writefinalstats()
else:
tag2bind()
atexit.register(go, 1)
def buildfncache(globals, cache):
if hasattr(types.IntType, '__dict__'):
clstypes = (types.ClassType, types.TypeType)
else:
clstypes = types.ClassType
for x in globals.values():
if isinstance(x, types.MethodType):
x = x.im_func
if isinstance(x, types.FunctionType):
cache[x.func_code] = x, ''
elif isinstance(x, clstypes):
for y in x.__dict__.values():
if isinstance(y, types.MethodType):
y = y.im_func
if isinstance(y, types.FunctionType):
cache[y.func_code] = y, x.__name__
# code-to-function mapping (cache)
function_cache = {}
def trytobind(co, globals, log=1):
try:
f, clsname = function_cache[co]
except KeyError:
buildfncache(globals, function_cache)
try:
f, clsname = function_cache[co]
except KeyError:
if logger:
logger.write('warning: cannot find function %s in %s' %
(co.co_name, globals.get('__name__', '?')), 3)
return # give up
if logger and log:
modulename = globals.get('__name__', '?')
if clsname:
modulename += '.' + clsname
logger.write('bind function: %s.%s' % (modulename, co.co_name), 1)
f.func_code = _psyco.proxycode(f)
# the list of code objects that have been tagged
tagged_codes = []
def tag(co, globals):
if logger:
try:
f, clsname = function_cache[co]
except KeyError:
buildfncache(globals, function_cache)
try:
f, clsname = function_cache[co]
except KeyError:
clsname = '' # give up
modulename = globals.get('__name__', '?')
if clsname:
modulename += '.' + clsname
logger.write('tag function: %s.%s' % (modulename, co.co_name), 1)
tagged_codes.append((co, globals))
_psyco.turbo_frame(co)
_psyco.turbo_code(co)
def tag2bind():
if tagged_codes:
if logger:
logger.write('profiling stopped, binding %d functions' %
len(tagged_codes), 2)
for co, globals in tagged_codes:
trytobind(co, globals, 0)
function_cache.clear()
del tagged_codes[:]
class Profiler:
MemoryTimerResolution = 0.103
def run(self, memory, time, memorymax, timemax):
self.memory = memory
self.memorymax = memorymax
self.time = time
if timemax is None:
self.endtime = None
else:
self.endtime = now() + timemax
self.alarms = []
profilers.append(self)
go()
def start(self):
curmem = _psyco.memory()
memlimits = []
if self.memorymax is not None:
if curmem >= self.memorymax:
if logger:
logger.writememory()
return self.limitreached('memorymax')
memlimits.append(self.memorymax)
if self.memory is not None:
if self.memory <= 0:
if logger:
logger.writememory()
return self.limitreached('memory')
memlimits.append(curmem + self.memory)
self.memory_at_start = curmem
curtime = now()
timelimits = []
if self.endtime is not None:
if curtime >= self.endtime:
return self.limitreached('timemax')
timelimits.append(self.endtime - curtime)
if self.time is not None:
if self.time <= 0.0:
return self.limitreached('time')
timelimits.append(self.time)
self.time_at_start = curtime
try:
self.do_start()
except error, e:
if logger:
logger.write('%s: disabled by psyco.error:' % (
self.__class__.__name__), 4)
logger.write(' %s' % str(e), 3)
return 0
if memlimits:
self.memlimits_args = (time.sleep, (self.MemoryTimerResolution,),
self.check_memory, (min(memlimits),))
self.alarms.append(_psyco.alarm(*self.memlimits_args))
if timelimits:
self.alarms.append(_psyco.alarm(time.sleep, (min(timelimits),),
self.time_out))
return 1
def stop(self):
for alarm in self.alarms:
alarm.stop(0)
for alarm in self.alarms:
alarm.stop(1) # wait for parallel threads to stop
del self.alarms[:]
if self.time is not None:
self.time -= now() - self.time_at_start
if self.memory is not None:
self.memory -= _psyco.memory() - self.memory_at_start
try:
self.do_stop()
except error:
return 0
return 1
def check_memory(self, limit):
if _psyco.memory() < limit:
return self.memlimits_args
go()
def time_out(self):
self.time = 0.0
go()
def limitreached(self, limitname):
try:
profilers.remove(self)
except ValueError:
pass
if logger:
logger.write('%s: disabled (%s limit reached)' % (
self.__class__.__name__, limitname), 4)
return 0
class FullCompiler(Profiler):
def do_start(self):
_psyco.profiling('f')
def do_stop(self):
_psyco.profiling('.')
class RunOnly(Profiler):
def do_start(self):
_psyco.profiling('n')
def do_stop(self):
_psyco.profiling('.')
class ChargeProfiler(Profiler):
def __init__(self, watermark, parentframe):
self.watermark = watermark
self.parent2 = parentframe * 2.0
self.lock = thread.allocate_lock()
def init_charges(self):
_psyco.statwrite(watermark = self.watermark,
parent2 = self.parent2)
def do_stop(self):
_psyco.profiling('.')
_psyco.statwrite(callback = None)
class ActiveProfiler(ChargeProfiler):
def active_start(self):
_psyco.profiling('p')
def do_start(self):
self.init_charges()
self.active_start()
_psyco.statwrite(callback = self.charge_callback)
def charge_callback(self, frame, charge):
tag(frame.f_code, frame.f_globals)
class PassiveProfiler(ChargeProfiler):
initial_charge_unit = _psyco.statread('unit')
reset_stats_after = 120 # half-lives (maximum 200!)
reset_limit = initial_charge_unit * (2.0 ** reset_stats_after)
def __init__(self, watermark, halflife, pollfreq, parentframe):
ChargeProfiler.__init__(self, watermark, parentframe)
self.pollfreq = pollfreq
# self.progress is slightly more than 1.0, and computed so that
# do_profile() will double the change_unit every 'halflife' seconds.
self.progress = 2.0 ** (1.0 / (halflife * pollfreq))
def reset(self):
_psyco.statwrite(unit = self.initial_charge_unit, callback = None)
_psyco.statreset()
if logger:
logger.write("%s: resetting stats" % self.__class__.__name__, 1)
def passive_start(self):
self.passivealarm_args = (time.sleep, (1.0 / self.pollfreq,),
self.do_profile)
self.alarms.append(_psyco.alarm(*self.passivealarm_args))
def do_start(self):
tag2bind()
self.init_charges()
self.passive_start()
def do_profile(self):
_psyco.statcollect()
if logger:
logger.dumpcharges()
nunit = _psyco.statread('unit') * self.progress
if nunit > self.reset_limit:
self.reset()
else:
_psyco.statwrite(unit = nunit, callback = self.charge_callback)
return self.passivealarm_args
def charge_callback(self, frame, charge):
trytobind(frame.f_code, frame.f_globals)
class ActivePassiveProfiler(PassiveProfiler, ActiveProfiler):
def do_start(self):
self.init_charges()
self.active_start()
self.passive_start()
def charge_callback(self, frame, charge):
tag(frame.f_code, frame.f_globals)
#
# we register our own version of sys.settrace(), sys.setprofile()
# and thread.start_new_thread().
#
def psyco_settrace(*args, **kw):
"This is the Psyco-aware version of sys.settrace()."
result = original_settrace(*args, **kw)
go()
return result
def psyco_setprofile(*args, **kw):
"This is the Psyco-aware version of sys.setprofile()."
result = original_setprofile(*args, **kw)
go()
return result
def psyco_thread_stub(callable, args, kw):
_psyco.statcollect()
if kw is None:
return callable(*args)
else:
return callable(*args, **kw)
def psyco_start_new_thread(callable, args, kw=None):
"This is the Psyco-aware version of thread.start_new_thread()."
return original_start_new_thread(psyco_thread_stub, (callable, args, kw))
original_settrace = sys.settrace
original_setprofile = sys.setprofile
original_start_new_thread = thread.start_new_thread
sys.settrace = psyco_settrace
sys.setprofile = psyco_setprofile
thread.start_new_thread = psyco_start_new_thread
# hack to patch threading._start_new_thread if the module is
# already loaded
if ('threading' in sys.modules and
hasattr(sys.modules['threading'], '_start_new_thread')):
sys.modules['threading']._start_new_thread = psyco_start_new_thread

View file

@ -0,0 +1,191 @@
###########################################################################
#
# Psyco general support module.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco general support module.
For internal use.
"""
###########################################################################
import sys, _psyco, __builtin__
error = _psyco.error
class warning(Warning):
pass
_psyco.NoLocalsWarning = warning
def warn(msg):
from warnings import warn
warn(msg, warning, stacklevel=2)
#
# Version checks
#
__version__ = 0x010600f0
if _psyco.PSYVER != __version__:
raise error, "version mismatch between Psyco parts, reinstall it"
version_info = (__version__ >> 24,
(__version__ >> 16) & 0xff,
(__version__ >> 8) & 0xff,
{0xa0: 'alpha',
0xb0: 'beta',
0xc0: 'candidate',
0xf0: 'final'}[__version__ & 0xf0],
__version__ & 0xf)
VERSION_LIMITS = [0x02020200, # 2.2.2
0x02030000, # 2.3
0x02040000] # 2.4
if ([v for v in VERSION_LIMITS if v <= sys.hexversion] !=
[v for v in VERSION_LIMITS if v <= _psyco.PYVER ]):
if sys.hexversion < VERSION_LIMITS[0]:
warn("Psyco requires Python version 2.2.2 or later")
else:
warn("Psyco version does not match Python version. "
"Psyco must be updated or recompiled")
if hasattr(_psyco, 'ALL_CHECKS') and hasattr(_psyco, 'VERBOSE_LEVEL'):
print >> sys.stderr, ('psyco: running in debugging mode on %s' %
_psyco.PROCESSOR)
###########################################################################
# sys._getframe() gives strange results on a mixed Psyco- and Python-style
# stack frame. Psyco provides a replacement that partially emulates Python
# frames from Psyco frames. The new sys._getframe() may return objects of
# a custom "Psyco frame" type, which is a subtype of the normal frame type.
#
# The same problems require some other built-in functions to be replaced
# as well. Note that the local variables are not available in any
# dictionary with Psyco.
class Frame:
pass
class PythonFrame(Frame):
def __init__(self, frame):
self.__dict__.update({
'_frame': frame,
})
def __getattr__(self, attr):
if attr == 'f_back':
try:
result = embedframe(_psyco.getframe(self._frame))
except ValueError:
result = None
except error:
warn("f_back is skipping dead Psyco frames")
result = self._frame.f_back
self.__dict__['f_back'] = result
return result
else:
return getattr(self._frame, attr)
def __setattr__(self, attr, value):
setattr(self._frame, attr, value)
def __delattr__(self, attr):
delattr(self._frame, attr)
class PsycoFrame(Frame):
def __init__(self, tag):
self.__dict__.update({
'_tag' : tag,
'f_code' : tag[0],
'f_globals': tag[1],
})
def __getattr__(self, attr):
if attr == 'f_back':
try:
result = embedframe(_psyco.getframe(self._tag))
except ValueError:
result = None
elif attr == 'f_lineno':
result = self.f_code.co_firstlineno # better than nothing
elif attr == 'f_builtins':
result = self.f_globals['__builtins__']
elif attr == 'f_restricted':
result = self.f_builtins is not __builtins__
elif attr == 'f_locals':
raise AttributeError, ("local variables of functions run by Psyco "
"cannot be accessed in any way, sorry")
else:
raise AttributeError, ("emulated Psyco frames have "
"no '%s' attribute" % attr)
self.__dict__[attr] = result
return result
def __setattr__(self, attr, value):
raise AttributeError, "Psyco frame objects are read-only"
def __delattr__(self, attr):
if attr == 'f_trace':
# for bdb which relies on CPython frames exhibiting a slightly
# buggy behavior: you can 'del f.f_trace' as often as you like
# even without having set it previously.
return
raise AttributeError, "Psyco frame objects are read-only"
def embedframe(result):
if type(result) is type(()):
return PsycoFrame(result)
else:
return PythonFrame(result)
def _getframe(depth=0):
"""Return a frame object from the call stack. This is a replacement for
sys._getframe() which is aware of Psyco frames.
The returned objects are instances of either PythonFrame or PsycoFrame
instead of being real Python-level frame object, so that they can emulate
the common attributes of frame objects.
The original sys._getframe() ignoring Psyco frames altogether is stored in
psyco._getrealframe(). See also psyco._getemulframe()."""
# 'depth+1' to account for this _getframe() Python function
return embedframe(_psyco.getframe(depth+1))
def _getemulframe(depth=0):
"""As _getframe(), but the returned objects are real Python frame objects
emulating Psyco frames. Some of their attributes can be wrong or missing,
however."""
# 'depth+1' to account for this _getemulframe() Python function
return _psyco.getframe(depth+1, 1)
def patch(name, module=__builtin__):
f = getattr(_psyco, name)
org = getattr(module, name)
if org is not f:
setattr(module, name, f)
setattr(_psyco, 'original_' + name, org)
_getrealframe = sys._getframe
sys._getframe = _getframe
patch('globals')
patch('eval')
patch('execfile')
patch('locals')
patch('vars')
patch('dir')
patch('input')
_psyco.original_raw_input = raw_input
__builtin__.__in_psyco__ = 0==1 # False
if hasattr(_psyco, 'compact'):
import kdictproxy
_psyco.compactdictproxy = kdictproxy.compactdictproxy

View file

@ -0,0 +1,54 @@
###########################################################################
#
# Psyco top-level file of the Psyco package.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco -- the Python Specializing Compiler.
Typical usage: add the following lines to your application's main module,
preferably after the other imports:
try:
import psyco
psyco.full()
except ImportError:
print 'Psyco not installed, the program will just run slower'
"""
###########################################################################
#
# This module is present to make 'psyco' a package and to
# publish the main functions and variables.
#
# More documentation can be found in core.py.
#
# Try to import the dynamic-loading _psyco and report errors
try:
import _psyco
except ImportError, e:
extramsg = ''
import sys, imp
try:
file, filename, (suffix, mode, type) = imp.find_module('_psyco', __path__)
except ImportError:
ext = [suffix for suffix, mode, type in imp.get_suffixes()
if type == imp.C_EXTENSION]
if ext:
extramsg = (" (cannot locate the compiled extension '_psyco%s' "
"in the package path '%s')" % (ext[0], '; '.join(__path__)))
else:
extramsg = (" (check that the compiled extension '%s' is for "
"the correct Python version; this is Python %s)" %
(filename, sys.version.split()[0]))
raise ImportError, str(e) + extramsg
# Publish important data by importing them in the package
from support import __version__, error, warning, _getrealframe, _getemulframe
from support import version_info, __version__ as hexversion
from core import full, profile, background, runonly, stop, cannotcompile
from core import log, bind, unbind, proxy, unproxy, dumpcodebuf
from _psyco import setfilter
from _psyco import compact, compacttype

View file

@ -0,0 +1,42 @@
###########################################################################
#
# Psyco class support module.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco class support module.
'psyco.classes.psyobj' is an alternate Psyco-optimized root for classes.
Any class inheriting from it or using the metaclass '__metaclass__' might
get optimized specifically for Psyco. It is equivalent to call
psyco.bind() on the class object after its creation.
Importing everything from psyco.classes in a module will import the
'__metaclass__' name, so all classes defined after a
from psyco.classes import *
will automatically use the Psyco-optimized metaclass.
"""
###########################################################################
__all__ = ['psyobj', 'psymetaclass', '__metaclass__']
from _psyco import compacttype
import core
from types import FunctionType
class psymetaclass(compacttype):
"Psyco-optimized meta-class. Turns all methods into Psyco proxies."
def __new__(cls, name, bases, dict):
bindlist = dict.get('__psyco__bind__')
if bindlist is None:
bindlist = [key for key, value in dict.items()
if isinstance(value, FunctionType)]
for attr in bindlist:
dict[attr] = core.proxy(dict[attr])
return super(psymetaclass, cls).__new__(cls, name, bases, dict)
psyobj = psymetaclass("psyobj", (), {})
__metaclass__ = psymetaclass

View file

@ -0,0 +1,231 @@
###########################################################################
#
# Psyco main functions.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco main functions.
Here are the routines that you can use from your applications.
These are mostly interfaces to the C core, but they depend on
the Python version.
You can use these functions from the 'psyco' module instead of
'psyco.core', e.g.
import psyco
psyco.log('/tmp/psyco.log')
psyco.profile()
"""
###########################################################################
import _psyco
import types
from support import *
newfunction = types.FunctionType
newinstancemethod = types.MethodType
# Default charge profiler values
default_watermark = 0.09 # between 0.0 (0%) and 1.0 (100%)
default_halflife = 0.5 # seconds
default_pollfreq_profile = 20 # Hz
default_pollfreq_background = 100 # Hz -- a maximum for sleep's resolution
default_parentframe = 0.25 # should not be more than 0.5 (50%)
def full(memory=None, time=None, memorymax=None, timemax=None):
"""Compile as much as possible.
Typical use is for small scripts performing intensive computations
or string handling."""
import profiler
p = profiler.FullCompiler()
p.run(memory, time, memorymax, timemax)
def profile(watermark = default_watermark,
halflife = default_halflife,
pollfreq = default_pollfreq_profile,
parentframe = default_parentframe,
memory=None, time=None, memorymax=None, timemax=None):
"""Turn on profiling.
The 'watermark' parameter controls how easily running functions will
be compiled. The smaller the value, the more functions are compiled."""
import profiler
p = profiler.ActivePassiveProfiler(watermark, halflife,
pollfreq, parentframe)
p.run(memory, time, memorymax, timemax)
def background(watermark = default_watermark,
halflife = default_halflife,
pollfreq = default_pollfreq_background,
parentframe = default_parentframe,
memory=None, time=None, memorymax=None, timemax=None):
"""Turn on passive profiling.
This is a very lightweight mode in which only intensively computing
functions can be detected. The smaller the 'watermark', the more functions
are compiled."""
import profiler
p = profiler.PassiveProfiler(watermark, halflife, pollfreq, parentframe)
p.run(memory, time, memorymax, timemax)
def runonly(memory=None, time=None, memorymax=None, timemax=None):
"""Nonprofiler.
XXX check if this is useful and document."""
import profiler
p = profiler.RunOnly()
p.run(memory, time, memorymax, timemax)
def stop():
"""Turn off all automatic compilation. bind() calls remain in effect."""
import profiler
profiler.go([])
def log(logfile='', mode='w', top=10):
"""Enable logging to the given file.
If the file name is unspecified, a default name is built by appending
a 'log-psyco' extension to the main script name.
Mode is 'a' to append to a possibly existing file or 'w' to overwrite
an existing file. Note that the log file may grow quickly in 'a' mode."""
import profiler, logger
if not logfile:
import os
logfile, dummy = os.path.splitext(sys.argv[0])
if os.path.basename(logfile):
logfile += '.'
logfile += 'log-psyco'
if hasattr(_psyco, 'VERBOSE_LEVEL'):
print >> sys.stderr, 'psyco: logging to', logfile
# logger.current should be a real file object; subtle problems
# will show up if its write() and flush() methods are written
# in Python, as Psyco will invoke them while compiling.
logger.current = open(logfile, mode)
logger.print_charges = top
profiler.logger = logger
logger.writedate('Logging started')
cannotcompile(logger.psycowrite)
_psyco.statwrite(logger=logger.psycowrite)
def bind(x, rec=None):
"""Enable compilation of the given function, method, or class object.
If C is a class (or anything with a '__dict__' attribute), bind(C) will
rebind all functions and methods found in C.__dict__ (which means, for
classes, all methods defined in the class but not in its parents).
The optional second argument specifies the number of recursive
compilation levels: all functions called by func are compiled
up to the given depth of indirection."""
if isinstance(x, types.MethodType):
x = x.im_func
if isinstance(x, types.FunctionType):
if rec is None:
x.func_code = _psyco.proxycode(x)
else:
x.func_code = _psyco.proxycode(x, rec)
return
if hasattr(x, '__dict__'):
funcs = [o for o in x.__dict__.values()
if isinstance(o, types.MethodType)
or isinstance(o, types.FunctionType)]
if not funcs:
raise error, ("nothing bindable found in %s object" %
type(x).__name__)
for o in funcs:
bind(o, rec)
return
raise TypeError, "cannot bind %s objects" % type(x).__name__
def unbind(x):
"""Reverse of bind()."""
if isinstance(x, types.MethodType):
x = x.im_func
if isinstance(x, types.FunctionType):
try:
f = _psyco.unproxycode(x.func_code)
except error:
pass
else:
x.func_code = f.func_code
return
if hasattr(x, '__dict__'):
for o in x.__dict__.values():
if (isinstance(o, types.MethodType)
or isinstance(o, types.FunctionType)):
unbind(o)
return
raise TypeError, "cannot unbind %s objects" % type(x).__name__
def proxy(x, rec=None):
"""Return a Psyco-enabled copy of the function.
The original function is still available for non-compiled calls.
The optional second argument specifies the number of recursive
compilation levels: all functions called by func are compiled
up to the given depth of indirection."""
if isinstance(x, types.FunctionType):
if rec is None:
code = _psyco.proxycode(x)
else:
code = _psyco.proxycode(x, rec)
return newfunction(code, x.func_globals, x.func_name)
if isinstance(x, types.MethodType):
p = proxy(x.im_func, rec)
return newinstancemethod(p, x.im_self, x.im_class)
raise TypeError, "cannot proxy %s objects" % type(x).__name__
def unproxy(proxy):
"""Return a new copy of the original function of method behind a proxy.
The result behaves like the original function in that calling it
does not trigger compilation nor execution of any compiled code."""
if isinstance(proxy, types.FunctionType):
return _psyco.unproxycode(proxy.func_code)
if isinstance(proxy, types.MethodType):
f = unproxy(proxy.im_func)
return newinstancemethod(f, proxy.im_self, proxy.im_class)
raise TypeError, "%s objects cannot be proxies" % type(proxy).__name__
def cannotcompile(x):
"""Instruct Psyco never to compile the given function, method
or code object."""
if isinstance(x, types.MethodType):
x = x.im_func
if isinstance(x, types.FunctionType):
x = x.func_code
if isinstance(x, types.CodeType):
_psyco.cannotcompile(x)
else:
raise TypeError, "unexpected %s object" % type(x).__name__
def dumpcodebuf():
"""Write in file psyco.dump a copy of the emitted machine code,
provided Psyco was compiled with a non-zero CODE_DUMP.
See py-utils/httpxam.py to examine psyco.dump."""
if hasattr(_psyco, 'dumpcodebuf'):
_psyco.dumpcodebuf()
###########################################################################
# Psyco variables
# error * the error raised by Psyco
# warning * the warning raised by Psyco
# __in_psyco__ * a new built-in variable which is always zero, but which
# Psyco special-cases by returning 1 instead. So
# __in_psyco__ can be used in a function to know if
# that function is being executed by Psyco or not.

View file

@ -0,0 +1,133 @@
###########################################################################
#
# Support code for the 'psyco.compact' type.
from __future__ import generators
try:
from UserDict import DictMixin
except ImportError:
# backported from Python 2.3 to Python 2.2
class DictMixin:
# Mixin defining all dictionary methods for classes that already have
# a minimum dictionary interface including getitem, setitem, delitem,
# and keys. Without knowledge of the subclass constructor, the mixin
# does not define __init__() or copy(). In addition to the four base
# methods, progressively more efficiency comes with defining
# __contains__(), __iter__(), and iteritems().
# second level definitions support higher levels
def __iter__(self):
for k in self.keys():
yield k
def has_key(self, key):
try:
value = self[key]
except KeyError:
return False
return True
def __contains__(self, key):
return self.has_key(key)
# third level takes advantage of second level definitions
def iteritems(self):
for k in self:
yield (k, self[k])
def iterkeys(self):
return self.__iter__()
# fourth level uses definitions from lower levels
def itervalues(self):
for _, v in self.iteritems():
yield v
def values(self):
return [v for _, v in self.iteritems()]
def items(self):
return list(self.iteritems())
def clear(self):
for key in self.keys():
del self[key]
def setdefault(self, key, default):
try:
return self[key]
except KeyError:
self[key] = default
return default
def pop(self, key, *args):
if len(args) > 1:
raise TypeError, "pop expected at most 2 arguments, got "\
+ repr(1 + len(args))
try:
value = self[key]
except KeyError:
if args:
return args[0]
raise
del self[key]
return value
def popitem(self):
try:
k, v = self.iteritems().next()
except StopIteration:
raise KeyError, 'container is empty'
del self[k]
return (k, v)
def update(self, other):
# Make progressively weaker assumptions about "other"
if hasattr(other, 'iteritems'): # iteritems saves memory and lookups
for k, v in other.iteritems():
self[k] = v
elif hasattr(other, '__iter__'): # iter saves memory
for k in other:
self[k] = other[k]
else:
for k in other.keys():
self[k] = other[k]
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __repr__(self):
return repr(dict(self.iteritems()))
def __cmp__(self, other):
if other is None:
return 1
if isinstance(other, DictMixin):
other = dict(other.iteritems())
return cmp(dict(self.iteritems()), other)
def __len__(self):
return len(self.keys())
###########################################################################
from _psyco import compact
class compactdictproxy(DictMixin):
def __init__(self, ko):
self._ko = ko # compact object of which 'self' is the dict
def __getitem__(self, key):
return compact.__getslot__(self._ko, key)
def __setitem__(self, key, value):
compact.__setslot__(self._ko, key, value)
def __delitem__(self, key):
compact.__delslot__(self._ko, key)
def keys(self):
return compact.__members__.__get__(self._ko)
def clear(self):
keys = self.keys()
keys.reverse()
for key in keys:
del self[key]
def __repr__(self):
keys = ', '.join(self.keys())
return '<compactdictproxy object {%s}>' % (keys,)

View file

@ -0,0 +1,96 @@
###########################################################################
#
# Psyco logger.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco logger.
See log() in core.py.
"""
###########################################################################
import _psyco
from time import time, localtime, strftime
current = None
print_charges = 10
dump_delay = 0.2
dump_last = 0.0
def write(s, level):
t = time()
f = t-int(t)
try:
current.write("%s.%02d %-*s%s\n" % (
strftime("%X", localtime(int(t))),
int(f*100.0), 63-level, s,
"%"*level))
current.flush()
except (OSError, IOError):
pass
def psycowrite(s):
t = time()
f = t-int(t)
try:
current.write("%s.%02d %-*s%s\n" % (
strftime("%X", localtime(int(t))),
int(f*100.0), 60, s.strip(),
"% %"))
current.flush()
except (OSError, IOError):
pass
##def writelines(lines, level=0):
## if lines:
## t = time()
## f = t-int(t)
## timedesc = strftime("%x %X", localtime(int(t)))
## print >> current, "%s.%03d %-*s %s" % (
## timedesc, int(f*1000),
## 50-level, lines[0],
## "+"*level)
## timedesc = " " * (len(timedesc)+5)
## for line in lines[1:]:
## print >> current, timedesc, line
def writememory():
write("memory usage: %d+ kb" % _psyco.memory(), 1)
def dumpcharges():
global dump_last
if print_charges:
t = time()
if not (dump_last <= t < dump_last+dump_delay):
if t <= dump_last+1.5*dump_delay:
dump_last += dump_delay
else:
dump_last = t
#write("%s: charges:" % who, 0)
lst = _psyco.stattop(print_charges)
if lst:
f = t-int(t)
lines = ["%s.%02d ______\n" % (
strftime("%X", localtime(int(t))),
int(f*100.0))]
i = 1
for co, charge in lst:
detail = co.co_filename
if len(detail) > 19:
detail = '...' + detail[-17:]
lines.append(" #%-3d |%4.1f %%| %-26s%20s:%d\n" %
(i, charge*100.0, co.co_name, detail,
co.co_firstlineno))
i += 1
current.writelines(lines)
current.flush()
def writefinalstats():
dumpcharges()
writememory()
writedate("program exit")
def writedate(msg):
write('%s, %s' % (msg, strftime("%x")), 20)

View file

@ -0,0 +1,379 @@
###########################################################################
#
# Psyco profiler (Python part).
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco profiler (Python part).
The implementation of the non-time-critical parts of the profiler.
See profile() and full() in core.py for the easy interface.
"""
###########################################################################
import _psyco
from support import *
import math, time, types, atexit
now = time.time
try:
import thread
except ImportError:
import dummy_thread as thread
# current profiler instance
current = None
# enabled profilers, in order of priority
profilers = []
# logger module (when enabled by core.log())
logger = None
# a lock for a thread-safe go()
go_lock = thread.allocate_lock()
def go(stop=0):
# run the highest-priority profiler in 'profilers'
global current
go_lock.acquire()
try:
prev = current
if stop:
del profilers[:]
if prev:
if profilers and profilers[0] is prev:
return # best profiler already running
prev.stop()
current = None
for p in profilers[:]:
if p.start():
current = p
if logger: # and p is not prev:
logger.write("%s: starting" % p.__class__.__name__, 5)
return
finally:
go_lock.release()
# no profiler is running now
if stop:
if logger:
logger.writefinalstats()
else:
tag2bind()
atexit.register(go, 1)
def buildfncache(globals, cache):
if hasattr(types.IntType, '__dict__'):
clstypes = (types.ClassType, types.TypeType)
else:
clstypes = types.ClassType
for x in globals.values():
if isinstance(x, types.MethodType):
x = x.im_func
if isinstance(x, types.FunctionType):
cache[x.func_code] = x, ''
elif isinstance(x, clstypes):
for y in x.__dict__.values():
if isinstance(y, types.MethodType):
y = y.im_func
if isinstance(y, types.FunctionType):
cache[y.func_code] = y, x.__name__
# code-to-function mapping (cache)
function_cache = {}
def trytobind(co, globals, log=1):
try:
f, clsname = function_cache[co]
except KeyError:
buildfncache(globals, function_cache)
try:
f, clsname = function_cache[co]
except KeyError:
if logger:
logger.write('warning: cannot find function %s in %s' %
(co.co_name, globals.get('__name__', '?')), 3)
return # give up
if logger and log:
modulename = globals.get('__name__', '?')
if clsname:
modulename += '.' + clsname
logger.write('bind function: %s.%s' % (modulename, co.co_name), 1)
f.func_code = _psyco.proxycode(f)
# the list of code objects that have been tagged
tagged_codes = []
def tag(co, globals):
if logger:
try:
f, clsname = function_cache[co]
except KeyError:
buildfncache(globals, function_cache)
try:
f, clsname = function_cache[co]
except KeyError:
clsname = '' # give up
modulename = globals.get('__name__', '?')
if clsname:
modulename += '.' + clsname
logger.write('tag function: %s.%s' % (modulename, co.co_name), 1)
tagged_codes.append((co, globals))
_psyco.turbo_frame(co)
_psyco.turbo_code(co)
def tag2bind():
if tagged_codes:
if logger:
logger.write('profiling stopped, binding %d functions' %
len(tagged_codes), 2)
for co, globals in tagged_codes:
trytobind(co, globals, 0)
function_cache.clear()
del tagged_codes[:]
class Profiler:
MemoryTimerResolution = 0.103
def run(self, memory, time, memorymax, timemax):
self.memory = memory
self.memorymax = memorymax
self.time = time
if timemax is None:
self.endtime = None
else:
self.endtime = now() + timemax
self.alarms = []
profilers.append(self)
go()
def start(self):
curmem = _psyco.memory()
memlimits = []
if self.memorymax is not None:
if curmem >= self.memorymax:
if logger:
logger.writememory()
return self.limitreached('memorymax')
memlimits.append(self.memorymax)
if self.memory is not None:
if self.memory <= 0:
if logger:
logger.writememory()
return self.limitreached('memory')
memlimits.append(curmem + self.memory)
self.memory_at_start = curmem
curtime = now()
timelimits = []
if self.endtime is not None:
if curtime >= self.endtime:
return self.limitreached('timemax')
timelimits.append(self.endtime - curtime)
if self.time is not None:
if self.time <= 0.0:
return self.limitreached('time')
timelimits.append(self.time)
self.time_at_start = curtime
try:
self.do_start()
except error, e:
if logger:
logger.write('%s: disabled by psyco.error:' % (
self.__class__.__name__), 4)
logger.write(' %s' % str(e), 3)
return 0
if memlimits:
self.memlimits_args = (time.sleep, (self.MemoryTimerResolution,),
self.check_memory, (min(memlimits),))
self.alarms.append(_psyco.alarm(*self.memlimits_args))
if timelimits:
self.alarms.append(_psyco.alarm(time.sleep, (min(timelimits),),
self.time_out))
return 1
def stop(self):
for alarm in self.alarms:
alarm.stop(0)
for alarm in self.alarms:
alarm.stop(1) # wait for parallel threads to stop
del self.alarms[:]
if self.time is not None:
self.time -= now() - self.time_at_start
if self.memory is not None:
self.memory -= _psyco.memory() - self.memory_at_start
try:
self.do_stop()
except error:
return 0
return 1
def check_memory(self, limit):
if _psyco.memory() < limit:
return self.memlimits_args
go()
def time_out(self):
self.time = 0.0
go()
def limitreached(self, limitname):
try:
profilers.remove(self)
except ValueError:
pass
if logger:
logger.write('%s: disabled (%s limit reached)' % (
self.__class__.__name__, limitname), 4)
return 0
class FullCompiler(Profiler):
def do_start(self):
_psyco.profiling('f')
def do_stop(self):
_psyco.profiling('.')
class RunOnly(Profiler):
def do_start(self):
_psyco.profiling('n')
def do_stop(self):
_psyco.profiling('.')
class ChargeProfiler(Profiler):
def __init__(self, watermark, parentframe):
self.watermark = watermark
self.parent2 = parentframe * 2.0
self.lock = thread.allocate_lock()
def init_charges(self):
_psyco.statwrite(watermark = self.watermark,
parent2 = self.parent2)
def do_stop(self):
_psyco.profiling('.')
_psyco.statwrite(callback = None)
class ActiveProfiler(ChargeProfiler):
def active_start(self):
_psyco.profiling('p')
def do_start(self):
self.init_charges()
self.active_start()
_psyco.statwrite(callback = self.charge_callback)
def charge_callback(self, frame, charge):
tag(frame.f_code, frame.f_globals)
class PassiveProfiler(ChargeProfiler):
initial_charge_unit = _psyco.statread('unit')
reset_stats_after = 120 # half-lives (maximum 200!)
reset_limit = initial_charge_unit * (2.0 ** reset_stats_after)
def __init__(self, watermark, halflife, pollfreq, parentframe):
ChargeProfiler.__init__(self, watermark, parentframe)
self.pollfreq = pollfreq
# self.progress is slightly more than 1.0, and computed so that
# do_profile() will double the change_unit every 'halflife' seconds.
self.progress = 2.0 ** (1.0 / (halflife * pollfreq))
def reset(self):
_psyco.statwrite(unit = self.initial_charge_unit, callback = None)
_psyco.statreset()
if logger:
logger.write("%s: resetting stats" % self.__class__.__name__, 1)
def passive_start(self):
self.passivealarm_args = (time.sleep, (1.0 / self.pollfreq,),
self.do_profile)
self.alarms.append(_psyco.alarm(*self.passivealarm_args))
def do_start(self):
tag2bind()
self.init_charges()
self.passive_start()
def do_profile(self):
_psyco.statcollect()
if logger:
logger.dumpcharges()
nunit = _psyco.statread('unit') * self.progress
if nunit > self.reset_limit:
self.reset()
else:
_psyco.statwrite(unit = nunit, callback = self.charge_callback)
return self.passivealarm_args
def charge_callback(self, frame, charge):
trytobind(frame.f_code, frame.f_globals)
class ActivePassiveProfiler(PassiveProfiler, ActiveProfiler):
def do_start(self):
self.init_charges()
self.active_start()
self.passive_start()
def charge_callback(self, frame, charge):
tag(frame.f_code, frame.f_globals)
#
# we register our own version of sys.settrace(), sys.setprofile()
# and thread.start_new_thread().
#
def psyco_settrace(*args, **kw):
"This is the Psyco-aware version of sys.settrace()."
result = original_settrace(*args, **kw)
go()
return result
def psyco_setprofile(*args, **kw):
"This is the Psyco-aware version of sys.setprofile()."
result = original_setprofile(*args, **kw)
go()
return result
def psyco_thread_stub(callable, args, kw):
_psyco.statcollect()
if kw is None:
return callable(*args)
else:
return callable(*args, **kw)
def psyco_start_new_thread(callable, args, kw=None):
"This is the Psyco-aware version of thread.start_new_thread()."
return original_start_new_thread(psyco_thread_stub, (callable, args, kw))
original_settrace = sys.settrace
original_setprofile = sys.setprofile
original_start_new_thread = thread.start_new_thread
sys.settrace = psyco_settrace
sys.setprofile = psyco_setprofile
thread.start_new_thread = psyco_start_new_thread
# hack to patch threading._start_new_thread if the module is
# already loaded
if ('threading' in sys.modules and
hasattr(sys.modules['threading'], '_start_new_thread')):
sys.modules['threading']._start_new_thread = psyco_start_new_thread

View file

@ -0,0 +1,191 @@
###########################################################################
#
# Psyco general support module.
# Copyright (C) 2001-2002 Armin Rigo et.al.
"""Psyco general support module.
For internal use.
"""
###########################################################################
import sys, _psyco, __builtin__
error = _psyco.error
class warning(Warning):
pass
_psyco.NoLocalsWarning = warning
def warn(msg):
from warnings import warn
warn(msg, warning, stacklevel=2)
#
# Version checks
#
__version__ = 0x010600f0
if _psyco.PSYVER != __version__:
raise error, "version mismatch between Psyco parts, reinstall it"
version_info = (__version__ >> 24,
(__version__ >> 16) & 0xff,
(__version__ >> 8) & 0xff,
{0xa0: 'alpha',
0xb0: 'beta',
0xc0: 'candidate',
0xf0: 'final'}[__version__ & 0xf0],
__version__ & 0xf)
VERSION_LIMITS = [0x02020200, # 2.2.2
0x02030000, # 2.3
0x02040000] # 2.4
if ([v for v in VERSION_LIMITS if v <= sys.hexversion] !=
[v for v in VERSION_LIMITS if v <= _psyco.PYVER ]):
if sys.hexversion < VERSION_LIMITS[0]:
warn("Psyco requires Python version 2.2.2 or later")
else:
warn("Psyco version does not match Python version. "
"Psyco must be updated or recompiled")
if hasattr(_psyco, 'ALL_CHECKS') and hasattr(_psyco, 'VERBOSE_LEVEL'):
print >> sys.stderr, ('psyco: running in debugging mode on %s' %
_psyco.PROCESSOR)
###########################################################################
# sys._getframe() gives strange results on a mixed Psyco- and Python-style
# stack frame. Psyco provides a replacement that partially emulates Python
# frames from Psyco frames. The new sys._getframe() may return objects of
# a custom "Psyco frame" type, which is a subtype of the normal frame type.
#
# The same problems require some other built-in functions to be replaced
# as well. Note that the local variables are not available in any
# dictionary with Psyco.
class Frame:
pass
class PythonFrame(Frame):
def __init__(self, frame):
self.__dict__.update({
'_frame': frame,
})
def __getattr__(self, attr):
if attr == 'f_back':
try:
result = embedframe(_psyco.getframe(self._frame))
except ValueError:
result = None
except error:
warn("f_back is skipping dead Psyco frames")
result = self._frame.f_back
self.__dict__['f_back'] = result
return result
else:
return getattr(self._frame, attr)
def __setattr__(self, attr, value):
setattr(self._frame, attr, value)
def __delattr__(self, attr):
delattr(self._frame, attr)
class PsycoFrame(Frame):
def __init__(self, tag):
self.__dict__.update({
'_tag' : tag,
'f_code' : tag[0],
'f_globals': tag[1],
})
def __getattr__(self, attr):
if attr == 'f_back':
try:
result = embedframe(_psyco.getframe(self._tag))
except ValueError:
result = None
elif attr == 'f_lineno':
result = self.f_code.co_firstlineno # better than nothing
elif attr == 'f_builtins':
result = self.f_globals['__builtins__']
elif attr == 'f_restricted':
result = self.f_builtins is not __builtins__
elif attr == 'f_locals':
raise AttributeError, ("local variables of functions run by Psyco "
"cannot be accessed in any way, sorry")
else:
raise AttributeError, ("emulated Psyco frames have "
"no '%s' attribute" % attr)
self.__dict__[attr] = result
return result
def __setattr__(self, attr, value):
raise AttributeError, "Psyco frame objects are read-only"
def __delattr__(self, attr):
if attr == 'f_trace':
# for bdb which relies on CPython frames exhibiting a slightly
# buggy behavior: you can 'del f.f_trace' as often as you like
# even without having set it previously.
return
raise AttributeError, "Psyco frame objects are read-only"
def embedframe(result):
if type(result) is type(()):
return PsycoFrame(result)
else:
return PythonFrame(result)
def _getframe(depth=0):
"""Return a frame object from the call stack. This is a replacement for
sys._getframe() which is aware of Psyco frames.
The returned objects are instances of either PythonFrame or PsycoFrame
instead of being real Python-level frame object, so that they can emulate
the common attributes of frame objects.
The original sys._getframe() ignoring Psyco frames altogether is stored in
psyco._getrealframe(). See also psyco._getemulframe()."""
# 'depth+1' to account for this _getframe() Python function
return embedframe(_psyco.getframe(depth+1))
def _getemulframe(depth=0):
"""As _getframe(), but the returned objects are real Python frame objects
emulating Psyco frames. Some of their attributes can be wrong or missing,
however."""
# 'depth+1' to account for this _getemulframe() Python function
return _psyco.getframe(depth+1, 1)
def patch(name, module=__builtin__):
f = getattr(_psyco, name)
org = getattr(module, name)
if org is not f:
setattr(module, name, f)
setattr(_psyco, 'original_' + name, org)
_getrealframe = sys._getframe
sys._getframe = _getframe
patch('globals')
patch('eval')
patch('execfile')
patch('locals')
patch('vars')
patch('dir')
patch('input')
_psyco.original_raw_input = raw_input
__builtin__.__in_psyco__ = 0==1 # False
if hasattr(_psyco, 'compact'):
import kdictproxy
_psyco.compactdictproxy = kdictproxy.compactdictproxy

Binary file not shown.

View file

@ -0,0 +1,490 @@
#!/usr/bin/env python
# engine to remove drm from Kindle for Mac and Kindle for PC books
# for personal use for archiving and converting your ebooks
# PLEASE DO NOT PIRATE EBOOKS!
# We want all authors and publishers, and eBook stores to live
# long and prosperous lives but at the same time we just want to
# be able to read OUR books on whatever device we want and to keep
# readable for a long, long time
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
# and many many others
# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
# K4 or Mobi with DRM is no londer a multi-step process.
#
# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook
# then calibre must be installed on the same machine and in the same account as K4PC or K4M
# for the plugin version to function properly.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines
# and import that ZIP into Calibre using its plugin configuration GUI.
from __future__ import with_statement
__version__ = '1.1'
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
import os, csv, getopt
import binascii
import zlib
from struct import pack, unpack, unpack_from
#Exception Handling
class DrmException(Exception):
pass
#
# crypto digestroutines
#
import hashlib
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
# determine if we are running as a calibre plugin
if 'calibre' in sys.modules:
inCalibre = True
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
else:
inCalibre = False
#
# start of Kindle specific routines
#
if not inCalibre:
import mobidedrm
if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
global kindleDatabase
# Encode the bytes in data with the characters in map
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
# Hash the bytes in data and then encode the digest with the characters in map
def encodeHash(data,map):
return encode(MD5(data),map)
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ""
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
if sys.platform.startswith('win'):
return CryptUnprotectData(encryptedValue,"")
else:
cleartext = CryptUnprotectData(encryptedValue)
return decode(cleartext, charMap1)
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return result
# Print all the records from the kindle.info file (option -i)
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# PID generation routines
#
# Returns two bit at offset from a bit field
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
# Returns the six bits at offset from a bit field
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
# 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
# Encryption table used to generate the device PID
def generatePidEncryptionTable() :
table = []
for counter1 in range (0,0x100):
value = counter1
for counter2 in range (0,8):
if (value & 1 == 0) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table.append(value)
return table
# Seed value used to generate the device PID
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
# Generate the device PID
def generateDevicePID(table,dsn,nbRoll):
seed = generatePidSeed(table,dsn)
pidAscii = ""
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
pid[index] = pid[index] ^ ord(dsn[counter])
index = (index+1) %8
for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index]
return pidAscii
# convert from 8 digit PID to 10 digit PID with checksum
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
class MobiPeek:
def loadSection(self, section):
before, after = self.sections[section:section+2]
self.f.seek(before)
return self.f.read(after - before)
def __init__(self, filename):
self.f = file(filename, 'rb')
self.header = self.f.read(78)
self.ident = self.header[0x3C:0x3C+8]
if self.ident != 'BOOKMOBI' and self.ident != 'TEXtREAd':
raise DrmException('invalid file format')
self.num_sections, = unpack_from('>H', self.header, 76)
sections = self.f.read(self.num_sections*8)
self.sections = unpack_from('>%dL' % (self.num_sections*2), sections, 0)[::2] + (0xfffffff, )
self.sect0 = self.loadSection(0)
self.f.close()
def getBookTitle(self):
# get book title
toff, tlen = unpack('>II', self.sect0[0x54:0x5c])
tend = toff + tlen
title = self.sect0[toff:tend]
return title
def getexthData(self):
# if exth region exists then grab it
# get length of this header
length, type, codepage, unique_id, version = unpack('>LLLLL', self.sect0[20:40])
exth_flag, = unpack('>L', self.sect0[0x80:0x84])
exth = ''
if exth_flag & 0x40:
exth = self.sect0[16 + length:]
return exth
def isNotEncrypted(self):
lock_type, = unpack('>H', self.sect0[0xC:0xC+2])
if lock_type == 0:
return True
return False
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid.
def getK4Pids(exth, title):
global kindleDatabase
try:
kindleDatabase = parseKindleInfo()
except Exception as message:
print(message)
if kindleDatabase != None :
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
# Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode to calculate the DSN
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
print("\nDSN: " + DSN)
# Compute the device PID (for which I can tell, is used for nothing).
# But hey, stuff being printed out is apparently cool.
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
print("Device PID: " + checksumPid(devicePID))
# Compute book PID
exth_records = {}
nitems, = unpack('>I', exth[8:12])
pos = 12
# Parse the exth records, storing data indexed by type
for i in xrange(nitems):
type, size = unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
exth_records[type] = content
pos += size
# Grab the contents of the type 209 exth record
if exth_records[209] != None:
data = exth_records[209]
else:
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4 file?")
# Parse the 209 data to find the the exth record with the token data.
# The last character of the 209 data points to the record with the token.
# Always 208 from my experience, but I'll leave the logic in case that changes.
for i in xrange(len(data)):
if ord(data[i]) != 0:
if exth_records[ord(data[i])] != None:
token = exth_records[ord(data[i])]
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
print("Account Token: " + kindleAccountToken)
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
if exth_records[503] != None:
print "Pid for " + exth_records[503] + ": " + bookPID
else:
print "Pid for " + title + ":" + bookPID
return bookPID
raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
return null
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
import mobidedrm
print ('K4MobiDeDrm v%(__version__)s '
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
if len(argv)<3:
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
print "Usage:"
print " %s <infile> <outfile> [<pidnums>]" % argv[0]
return 1
if len(argv) == 4:
pidnums = argv[3]
if len(argv) == 3:
pidnums = ""
kindleDatabase = None
infile = argv[1]
outfile = argv[2]
try:
# first try with K4PC/K4M
ex = MobiPeek(infile)
if ex.isNotEncrypted():
print "File was Not Encrypted"
return 2
title = ex.getBookTitle()
exth = ex.getexthData()
pid = getK4Pids(exth, title)
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
except DrmException:
pass
except mobidedrm.DrmException:
pass
else:
file(outfile, 'wb').write(unlocked_file)
return 0
# now try from the pid list
pids = pidnums.split(',')
for pid in pids:
try:
print 'Trying: "'+ pid + '"'
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
except mobidedrm.DrmException:
pass
else:
file(outfile, 'wb').write(unlocked_file)
return 0
# we could not unencrypt book
print "Error: Could Not Unencrypt Book"
return 1
if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout)
sys.exit(main())
if not __name__ == "__main__" and inCalibre:
from calibre.customize import FileTypePlugin
class K4DeDRM(FileTypePlugin):
name = 'K4PC, K4Mac, Mobi DeDRM' # Name of the plugin
description = 'Removes DRM from K4PC, K4Mac, and Mobi files. \
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 0, 1) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
def run(self, path_to_ebook):
from calibre.gui2 import is_ok_to_use_qt
from PyQt4.Qt import QMessageBox
global kindleDatabase
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
import mobidedrm
pidnums = self.site_customization
# first try with book specifc pid from K4PC or K4M
try:
kindleDatabase = None
ex = MobiPeek(path_to_ebook)
if ex.isNotEncrypted():
return path_to_ebook
title = ex.getBookTitle()
exth = ex.getexthData()
pid = getK4Pids(exth, title)
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
except DrmException:
pass
except mobidedrm.DrmException:
pass
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
# now try from the pid list
pids = pidnums.split(',')
for pid in pids:
try:
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook, pid)
except mobidedrm.DrmException:
pass
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
#if you reached here then no luck raise and exception
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
d.show()
d.raise_()
d.exec_()
raise Exception("K4MobiDeDRM plugin could not decode the file")
return ""
def customization_help(self, gui=False):
return 'Enter each 10 character PID separated by a comma (no spaces).'

View file

@ -0,0 +1,319 @@
# standlone set of Mac OSX specific routines needed for K4DeDRM
from __future__ import with_statement
import sys
import os
#Exception Handling
class K4MDrmException(Exception):
pass
import signal
import threading
import subprocess
from subprocess import Popen, PIPE, STDOUT
# **heavily** chopped up and modfied version of asyncproc.py
# to make it actually work on Windows as well as Mac/Linux
# For the original see:
# "http://www.lysator.liu.se/~bellman/download/"
# author is "Thomas Bellman <bellman@lysator.liu.se>"
# available under GPL version 3 or Later
# create an asynchronous subprocess whose output can be collected in
# a non-blocking manner
# What a mess! Have to use threads just to get non-blocking io
# in a cross-platform manner
# luckily all thread use is hidden within this class
class Process(object):
def __init__(self, *params, **kwparams):
if len(params) <= 3:
kwparams.setdefault('stdin', subprocess.PIPE)
if len(params) <= 4:
kwparams.setdefault('stdout', subprocess.PIPE)
if len(params) <= 5:
kwparams.setdefault('stderr', subprocess.PIPE)
self.__pending_input = []
self.__collected_outdata = []
self.__collected_errdata = []
self.__exitstatus = None
self.__lock = threading.Lock()
self.__inputsem = threading.Semaphore(0)
self.__quit = False
self.__process = subprocess.Popen(*params, **kwparams)
if self.__process.stdin:
self.__stdin_thread = threading.Thread(
name="stdin-thread",
target=self.__feeder, args=(self.__pending_input,
self.__process.stdin))
self.__stdin_thread.setDaemon(True)
self.__stdin_thread.start()
if self.__process.stdout:
self.__stdout_thread = threading.Thread(
name="stdout-thread",
target=self.__reader, args=(self.__collected_outdata,
self.__process.stdout))
self.__stdout_thread.setDaemon(True)
self.__stdout_thread.start()
if self.__process.stderr:
self.__stderr_thread = threading.Thread(
name="stderr-thread",
target=self.__reader, args=(self.__collected_errdata,
self.__process.stderr))
self.__stderr_thread.setDaemon(True)
self.__stderr_thread.start()
def pid(self):
return self.__process.pid
def kill(self, signal):
self.__process.send_signal(signal)
# check on subprocess (pass in 'nowait') to act like poll
def wait(self, flag):
if flag.lower() == 'nowait':
rc = self.__process.poll()
else:
rc = self.__process.wait()
if rc != None:
if self.__process.stdin:
self.closeinput()
if self.__process.stdout:
self.__stdout_thread.join()
if self.__process.stderr:
self.__stderr_thread.join()
return self.__process.returncode
def terminate(self):
if self.__process.stdin:
self.closeinput()
self.__process.terminate()
# thread gets data from subprocess stdout
def __reader(self, collector, source):
while True:
data = os.read(source.fileno(), 65536)
self.__lock.acquire()
collector.append(data)
self.__lock.release()
if data == "":
source.close()
break
return
# thread feeds data to subprocess stdin
def __feeder(self, pending, drain):
while True:
self.__inputsem.acquire()
self.__lock.acquire()
if not pending and self.__quit:
drain.close()
self.__lock.release()
break
data = pending.pop(0)
self.__lock.release()
drain.write(data)
# non-blocking read of data from subprocess stdout
def read(self):
self.__lock.acquire()
outdata = "".join(self.__collected_outdata)
del self.__collected_outdata[:]
self.__lock.release()
return outdata
# non-blocking read of data from subprocess stderr
def readerr(self):
self.__lock.acquire()
errdata = "".join(self.__collected_errdata)
del self.__collected_errdata[:]
self.__lock.release()
return errdata
# non-blocking write to stdin of subprocess
def write(self, data):
if self.__process.stdin is None:
raise ValueError("Writing to process with stdin not a pipe")
self.__lock.acquire()
self.__pending_input.append(data)
self.__inputsem.release()
self.__lock.release()
# close stdinput of subprocess
def closeinput(self):
self.__lock.acquire()
self.__quit = True
self.__inputsem.release()
self.__lock.release()
# interface to needed routines in openssl's libcrypto
def _load_crypto_libcrypto():
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
from ctypes.util import find_library
libcrypto = find_library('crypto')
if libcrypto is None:
raise K4MDrmException('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
class LibCrypto(object):
def __init__(self):
self._blocksize = 0
self._keyctx = None
self.iv = 0
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise K4MDrmException('AES improper key used')
return
keyctx = self._keyctx = AES_KEY()
self.iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise K4MDrmException('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
if rv == 0:
raise K4MDrmException('AES decryption failed')
return out.raw
def keyivgen(self, passwd):
salt = '16743'
saltlen = 5
passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw
return LibCrypto
def _load_crypto():
LibCrypto = None
try:
LibCrypto = _load_crypto_libcrypto()
except (ImportError, K4MDrmException):
pass
return LibCrypto
LibCrypto = _load_crypto()
#
# Utility Routines
#
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns with the first found serial number in that class
def GetVolumeSerialNumber():
cmdline = '/usr/sbin/ioreg -r -c AppleAHCIDiskDriver'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
poll = p.wait('wait')
results = p.read()
reslst = results.split('\n')
sernum = '9999999999'
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('"Serial Number" = "')
if pp >= 0:
sernum = resline[pp+19:]
sernum = sernum[:-1]
sernum = sernum.lstrip()
break
return sernum
# uses unix env to get username instead of using sysctlbyname
def GetUserName():
username = os.getenv('USER')
return username
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
import hashlib
def SHA256(message):
ctx = hashlib.sha256()
ctx.update(message)
return ctx.digest()
# implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData):
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
crp = LibCrypto()
key_iv = crp.keyivgen(passwdData)
key = key_iv[0:32]
iv = key_iv[32:48]
crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData)
return cleartext
# Locate and open the .kindle-info file
def openKindleInfo():
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
poll = p1.wait('wait')
results = p1.read()
reslst = results.split('\n')
kinfopath = 'NONE'
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('.kindle-info')
if pp >= 0:
kinfopath = resline
break
if not os.path.exists(kinfopath):
raise K4MDrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r')

View file

@ -0,0 +1,107 @@
# K4PC Windows specific routines
from __future__ import with_statement
import sys, os
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
string_at, Structure, c_void_p, cast
import _winreg as winreg
import traceback
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class DrmException(Exception):
pass
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
def GetSystemDirectory():
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
GetSystemDirectoryW.restype = c_uint
def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1)
GetSystemDirectoryW(buffer, len(buffer))
return buffer.value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory()
def GetVolumeSerialNumber():
GetVolumeInformationW = kernel32.GetVolumeInformationW
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
POINTER(c_uint), POINTER(c_uint),
POINTER(c_uint), c_wchar_p, c_uint]
GetVolumeInformationW.restype = c_uint
def GetVolumeSerialNumber(path = GetSystemDirectory().split('\\')[0] + '\\'):
vsn = c_uint(0)
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
return str(vsn.value)
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetUserName():
GetUserNameW = advapi32.GetUserNameW
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
GetUserNameW.restype = c_uint
def GetUserName():
buffer = create_unicode_buffer(32)
size = c_uint(len(buffer))
while not GetUserNameW(buffer, byref(size)):
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
return buffer.value.encode('utf-16-le')[::2]
return GetUserName
GetUserName = GetUserName()
def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)):
raise DrmException("Failed to Unprotect Data")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
#
# Locate and open the Kindle.info file.
#
def openKindleInfo():
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')

View file

@ -39,8 +39,12 @@
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
# 0.17 - added modifications to support its use as an imported python module
# both inside calibre and also in other places (ie K4DeDRM tools)
# 0.17a - disabled the standalone plugin feature since a plugin can not import
# a plugin
__version__ = '0.16'
__version__ = '0.17'
import sys
import struct
@ -248,7 +252,42 @@ class DrmStripper:
def getResult(self):
return self.data_file
if not __name__ == "__main__":
def getUnencryptedBook(infile,pid):
sys.stdout=Unbuffered(sys.stdout)
data_file = file(infile, 'rb').read()
strippedFile = DrmStripper(data_file, pid)
return strippedFile.getResult()
def main(argv=sys.argv):
sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(argv)<4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " %s <infile> <outfile> <PID>" % sys.argv[0]
return 1
else:
infile = argv[1]
outfile = argv[2]
pid = argv[3]
try:
stripped_file = getUnencryptedBook(infile, pid)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
#if not __name__ == "__main__":
if False:
# note a calibre plugin can not import code with another calibre plugin
# in it as it ends up registering two different plugins
from calibre.customize import FileTypePlugin
class MobiDeDRM(FileTypePlugin):
@ -256,7 +295,7 @@ if not __name__ == "__main__":
description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin
version = (0, 1, 6) # The version number of this plugin
version = (0, 1, 7) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
@ -270,41 +309,17 @@ if not __name__ == "__main__":
try:
unlocked_file = DrmStripper(data_file, i).getResult()
except DrmException:
# ignore the error
pass
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
d.show()
d.raise_()
d.exec_()
raise Exception("MobiDeDRM Plugin: Error decoding ebook")
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
d.show()
d.raise_()
d.exec_()
return path_to_ebook
def customization_help(self, gui=False):
return 'Enter PID (separate multiple PIDs with comma)'
if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(sys.argv)<4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " %s <infile> <outfile> <PID>" % sys.argv[0]
sys.exit(1)
else:
infile = sys.argv[1]
outfile = sys.argv[2]
pid = sys.argv[3]
data_file = file(infile, 'rb').read()
try:
strippedFile = DrmStripper(data_file, pid)
file(outfile, 'wb').write(strippedFile.getResult())
except DrmException, e:
print "Error: %s" % e
sys.exit(1)
sys.exit(0)

View file

@ -1,186 +0,0 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
import sys
sys.path.append('lib')
import os, os.path, urllib
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
from scrolltextwidget import ScrolledText
class MainDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.root = root
self.interval = 2000
self.p2 = None
self.status = Tkinter.Label(self, text='Remove Encryption from a K4PC eBook')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='K4PC eBook input file').grid(row=0, sticky=Tkconstants.E)
self.mobipath = Tkinter.Entry(body, width=50)
self.mobipath.grid(row=0, column=1, sticky=sticky)
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
self.mobipath.insert(0, cwd)
button = Tkinter.Button(body, text="...", command=self.get_mobipath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Name for Unencrypted Output File').grid(row=1, sticky=Tkconstants.E)
self.outpath = Tkinter.Entry(body, width=50)
self.outpath.grid(row=1, column=1, sticky=sticky)
self.outpath.insert(0, '')
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=1, column=2)
msg1 = 'Conversion Log \n\n'
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD)
self.stext.grid(row=3, column=0, columnspan=2,sticky=sticky)
self.stext.insert(Tkconstants.END,msg1)
buttons = Tkinter.Frame(self)
buttons.pack()
self.sbotton = Tkinter.Button(
buttons, text="Start", width=10, command=self.convertit)
self.sbotton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
self.qbutton = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quitting)
self.qbutton.pack(side=Tkconstants.RIGHT)
# read from subprocess pipe without blocking
# invoked every interval via the widget "after"
# option being used, so need to reset it for the next time
def processPipe(self):
poll = self.p2.wait('nowait')
if poll != None:
text = self.p2.readerr()
text += self.p2.read()
msg = text + '\n\n' + 'Encryption successfully removed\n'
if poll != 0:
msg = text + '\n\n' + 'Error: Encryption Removal Failed\n'
self.showCmdOutput(msg)
self.p2 = None
self.sbotton.configure(state='normal')
return
text = self.p2.readerr()
text += self.p2.read()
self.showCmdOutput(text)
# make sure we get invoked again by event loop after interval
self.stext.after(self.interval,self.processPipe)
return
# post output from subprocess in scrolled text widget
def showCmdOutput(self, msg):
if msg and msg !='':
msg = msg.encode('utf-8')
self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END)
return
# run as a subprocess via pipes and collect stdout
def mobirdr(self, infile, outfile):
# os.putenv('PYTHONUNBUFFERED', '1')
cmdline = 'python ./lib/k4pcdedrm.py "' + infile + '" "' + outfile + '"'
if sys.platform[0:3] == 'win':
search_path = os.environ['PATH']
search_path = search_path.lower()
if search_path.find('python') >= 0:
cmdline = 'python lib\k4pcdedrm.py "' + infile + '" "' + outfile + '"'
else :
cmdline = 'lib\k4pcdedrm.py "' + infile + '" "' + outfile + '"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
return p2
def get_mobipath(self):
mobipath = tkFileDialog.askopenfilename(
parent=None, title='Select K4PC eBook File',
defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.azw'),('Mobi eBook File', '.mobi'),
('All Files', '.*')])
if mobipath:
mobipath = os.path.normpath(mobipath)
self.mobipath.delete(0, Tkconstants.END)
self.mobipath.insert(0, mobipath)
return
def get_outpath(self):
mobipath = self.mobipath.get()
initname = os.path.basename(mobipath)
p = initname.find('.')
if p >= 0: initname = initname[0:p]
initname += '_nodrm.mobi'
outpath = tkFileDialog.asksaveasfilename(
parent=None, title='Select Unencrypted Mobi File to produce',
defaultextension='.mobi', initialfile=initname,
filetypes=[('Mobi files', '.mobi'), ('All files', '.*')])
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return
def quitting(self):
# kill any still running subprocess
if self.p2 != None:
if (self.p2.wait('nowait') == None):
self.p2.terminate()
self.root.destroy()
# actually ready to run the subprocess and get its output
def convertit(self):
# now disable the button to prevent multiple launches
self.sbotton.configure(state='disabled')
mobipath = self.mobipath.get()
outpath = self.outpath.get()
if not mobipath or not os.path.exists(mobipath):
self.status['text'] = 'Specified K4PC eBook file does not exist'
self.sbotton.configure(state='normal')
return
if not outpath:
self.status['text'] = 'No output file specified'
self.sbotton.configure(state='normal')
return
log = 'Command = "python k4pcdedrm.py"\n'
log += 'K4PC Path = "'+ mobipath + '"\n'
log += 'Output File = "' + outpath + '"\n'
log += '\n\n'
log += 'Please Wait ...\n\n'
log = log.encode('utf-8')
self.stext.insert(Tkconstants.END,log)
self.p2 = self.mobirdr(mobipath, outpath)
# python does not seem to allow you to create
# your own eventloop which every other gui does - strange
# so need to use the widget "after" command to force
# event loop to run non-gui events every interval
self.stext.after(self.interval,self.processPipe)
return
def main(argv=None):
root = Tkinter.Tk()
root.title('K4PC eBook Encryption Removal')
root.resizable(True, False)
root.minsize(300, 0)
MainDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -1,88 +0,0 @@
#include <cstdlib>
#include <iostream>
#include <conio.h>
#include <fstream>
using namespace std;
int main(int argc, char *argv[])
{
// Variables
int TopazTrue = 0;
int strlength = 0;
char uinfile[80];
char outfile[80];
char command[80];
char buffer[80];
// String initialization
strcpy(uinfile,"");
strcpy(outfile,"");
strcpy(buffer,"");
strcpy(command,"skindle "); // string preloaded with "skindle "
cout << "\n\n\n Please enter the name of the book to be converted:\n\n ";
cout << " Don't forget the prc file extension!\n\n ";
cout << " Watch out for zeros and Os. Zeros are skinny and Os are fat.\n\n\n ";
cin >> uinfile; // get file name of the book to be converted from user
ifstream infile(uinfile);
infile.getline(buffer,4);
if (strncmp (buffer,"TPZ",3)==0) // open file and test first 3 char if TPZ then book is topaz
{
TopazTrue = 1; // This is a Topaz file
}
strlength = strlen(uinfile);
if(strlength > 13)
{
strncat(outfile,uinfile,10); // Create output file name using first 10 char of input file name
}
else
{
strncat(outfile,uinfile, (strlength - 4)); // If file name is less than 10 characters
}
if(TopazTrue == 1) // This is Topaz Book
{
strcat(command,"-d "); // Add the topaz switch to the command line
strcat(outfile,".tpz"); // give tpz file extension to topaz output file
} // end of TopazTrue
else
{
strcat(outfile,".azw");
} // if not Topaz make it azw
strcat(command,"-i "); // Add the input switch to the command line
strcat(command,uinfile); // add the input file name to the command line
strcat(command," -o "); // add the output switch to the command line
strcat(command,outfile); // Add the output file name to the command line
cout << "\n\n The skindle program is called here.\n";
cout << " Any errors reported between here and \"The command line used was:\"\n";
cout << " Are errors from the skindle program. Not EZskindle4PC.\n\n";
system(command); // call skindle program to convert the book
cout << "\n\n The command line used was:\n\n";
cout << " " << command << "\n";
cout << "\n\n\n Please note the output file is created from the input";
cout << "\n file name. The file extension is changed to tpz for Topaz";
cout << "\n files and to azw for non-Topaz files. Also, _EBOK is removed ";
cout << "\n from the file name. This is to make it eaiser to identify ";
cout << "\n the file with no DRM.";
system("PAUSE");
return EXIT_SUCCESS;
}

View file

@ -1,44 +0,0 @@
LZskindle4PCv1_1 The Lazy skindle program for those who are typing impared
To setup:
1. Create a new folder: example C:\skindle
2. Place LZskindle4PCv1_1.exe and skindle.exe in this folder.
3. Create 2 subfolders: C:\skindle\input
and C:\skindle\output
To run:
1. Copy the book(s) you wish to remove DRM from into the input directory
(leave the originals in my kindle folder).
2. Double click on LZskindle4PCv1_0.exe
3. A DOS window will open and will show skindle being called for
each book in the input directory.
4. The books with the DRM removed will now be in the output directory.
Rev1_1
fixed program to allow any file extension. My testing indicates that
skindle does not care what file extension a file has. If it is a file type
that it can convert it will. If the file is not compatible it will close
and give an unknown file type message.
Rev1_0
If the program is run with no files in the input directory you will get a
“File not Found” error and the program will terminate.
PLEASE USE ONLY FOR YOUR PERSONAL USE ON BOOKS YOU PAID FOR!!
This program is provided to allow you to archive your library in a format that
will outlast the kindle. Technology moves on and you should not have to reinvest
in an entire new library when the Kindle is obsolete. Please do not use this program
for piracy.

View file

@ -1,150 +0,0 @@
#include <cstdlib>
#include <iostream>
#include <fstream>
//#include <conio.h>
using namespace std;
int main(int argc, char *argv[])
{
// Variable Declarations ??
char buffer[80];
int error = 0;
// int YesNo = 0;
// int exit = 0;
// Variables EZskindle4PC
int TopazTrue = 0;
int strlength = 0;
char uinfile[80];
char outfile[80];
char command[80];
char buffer2[20];
char tempfile[80];
// Initialize strings
strcpy(uinfile,"");
strcpy(outfile,"");
strcpy(buffer,"");
strcpy(buffer2,"");
strcpy(command,"skindle "); // string preloaded with "skindle "
//// Beginning of program code ////////////////////////////////////////////////////////////
system("dir /b .\\input\\*.* > books.txt"); // Create txt file with list of books
// No testing of file type being done
// I am letting skindle determing if valid
// file type
// Read in the list of book file names
ifstream infile("books.txt");
do // while not end of file
{
infile.getline(buffer,50); // load the first 50 characters of the line to buffer
if(strcmp(buffer, "")!= 0) // If there is file name in the buffer do this on last loop buffer will be empty
{
strcpy(uinfile,buffer); // load file name from buffer
strcpy(tempfile,".\\input\\"); // load directory name for input files
strcat(tempfile,buffer); // load the file name
ifstream infile2(tempfile); // open the book file for reading
infile2.getline(buffer2,4); // load first 4 char from file
infile2.close(); // close the book file
if (strncmp (buffer2,"TPZ",3)==0) // open file and test first 3 char if TPZ then book is topaz
{
TopazTrue = 1; // This is a Topaz file
}
strlength = strlen(uinfile);
if(strlength > 13)
{
strncat(outfile,uinfile,10); // Create output file name using first 10 char of input file name
}
else
{
strncat(outfile,uinfile, (strlength - 4)); // If file name is less than 10 characters
}
if(TopazTrue == 1) // This is Topaz Book
{
strcat(command,"-d "); // Add the topaz switch to the command line
strcat(outfile,".tpz"); // give tpz file extension to topaz output file
} // end of TopazTrue
else
{
strcat(outfile,".azw");
} // if not Topaz make it azw
strcat(command,"-i "); // Add the input switch to the command line
strcat(command,".\\input\\"); // Add the input directory to the command line
strcat(command,uinfile); // add the input file name to the command line
strcat(command," -o "); // add the output switch to the command line
strcat(command,".\\output\\"); // Add directory for out files
strcat(command,outfile); // Add the output file name to the command line
cout << "\n\n The skindle program is called here.\n";
cout << " Any errors reported between here and \"The command line used was:\"\n";
cout << " Are errors from the skindle program. Not EZskindle4PC.\n\n";
system(command); // call skindle program to convert the book
cout << "\n\n The command line used was:\n\n";
cout << " " << command << "\n\n\n\n";
}// end of file name in the buffer required to prevent execution on EOF
strcpy(command,"skindle "); // reset strings and variables for next book
strcpy(outfile,"");
strcpy(uinfile,"");
strcpy(buffer,"");
strcpy(buffer2,"");
TopazTrue = 0;
strlength = 0;
}while (! infile.eof() ); // no more books in the file
infile.close(); // close books.txt
// cout << "\n\n\n Do you want to delete all of the books from the input directory?\n\n";
// cout << " DO NOT DELETE IF THESE ARE ONLY COPY OF YOUR BOOKS!!!!\n\n";
// cout << " Y or N: ";
// do { // while not yes or no
// YesNo = getch(); // This is a DOS/Windows console command not standard C may not be
// // Usable under Unix or Mac implementations
//
// if((YesNo == 121)||(YesNo == 89)) // y or Y is true
// {
// exit = 1; // valid input exit do while loop
// cout << "\n\n";
// system("del .\\input\\*.*"); // delete everything in the input directory
// cout << "\n\n";
// }
// if((YesNo == 110)||(YesNo == 78)) // n or N is true
// {
// exit = 1; // valid input exit do while loop
// }
//
// }while (exit != 1);
// cout << "\n\nYesNo = " << YesNo << "\n\n";
system("PAUSE");
system("del books.txt"); // Delete txt file with list of books
return EXIT_SUCCESS;
}

View file

@ -1,27 +0,0 @@
Ezskindle4PC.exe
This executable program makes using skindle easier for people using Windows PCs.
I do not know if it will work under any other operating system, however, I have included
the source code should anyone want to port it into other operating systems.
To use this program:
1. Copy the ezskindle4PC.exe into the same directory with the skindle files.
2. Copy the kindle book into the same directory.
3. double click the EZskindle4PCv1_0.exe file.
a. A DOS window will open and you will be asked for the name of the file you want to work with.
4. Type in the books file name. (it will look something like B000WCTBTA_EBOK.prc)
5. The program will then check if it is a Topaz file and then create the output file name using the
first part of the input file name. It will use “tpz” file extension for Topaz books and will use “azw”
for non topaz books. The files with the “azw” format can be converted to other ebook formats using
Calibre. If you want to convert Topaz books to other formats you need to use Topaz tools not skindle.
6. The program will then create a command line and call the skindle program to process the book and
remove the DRM.
7. The program will pause and allow you to see the result of the skindle process.
8. Press any key to close the program.
version 1.1
Ok
Found a new 32 bit compiler and I think I have worked out the kinks.

View file

@ -1,9 +0,0 @@
Readme.txt
- LZskindle4PC is a front end for running skindle that makes it easier for some people who do not like command lines to use it.
- skindle has completely reverse engineered how the book specific PID is generated for Kindle4PC so it can be very useful when new version of Kindle4PC are released and unswindle has not yet been updated. Unfortunately, skindle has some minor bugs that can actually result in corrupted eBooks. No one has yet tracked them down and fixed them. Until they do, use at your own risk.
- unswindle can be used to find the book specific PID but it needs to be updated for each version of Kindle4PC that Amazon releases (and therefore is also useful for Linux users who have Wine). This program “patches” the Kindle4PC executable and therefore is very release specific.
Unfortunately unswindle v7 the latest, has not been updated to work with the latest version of Kindle for PC. You will need to find one of the older versions of Kindle4PC and prevent later updates in order to use this tool.

View file

@ -1,23 +0,0 @@
OBJS=skindle.o md5.o sha1.o b64.o skinutils.o cbuf.o mobi.o tpz.o
CC=gcc
LD=gcc
EXE=skindle
EXTRALIBS=libz.a -lCrypt32 -lAdvapi32
CFLAGS=-mno-cygwin
#use the following to strip your binary
LDFLAGS=-s -mno-cygwin
#LDFLAGS=-mno-cygwin
all: $(EXE)
%.o: %.c
$(CC) -c $(CFLAGS) -g $(INC) $< -o $@
$(EXE): $(OBJS)
$(LD) $(LDFLAGS) -o $@ -g $(OBJS) $(EXTRALIBS)
clean:
-@rm *.o

View file

@ -1,85 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* Dependencies: zlib (included)
* build on cygwin using make and the included make file
* A fully functionaly windows executable is included
*/
/*
* MUST be run on the computer on which KindleForPC is installed
* under the account that was used to purchase DRM'ed content.
* Requires your kindle.info file which can be found in something like:
* <User home>\...\Amazon\Kindle For PC\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}
* where ... varies by platform but is "Local Settings\Application Data" on XP
* skindle will attempt to find this file automatically.
*/
/*
What: KindleForPC DRM removal utility to preserve your fair use rights!
Why: Fair use is a well established doctrine, and I am no fan of vendor
lockin.
How: This utility implements the PID extraction, DRM key generation and
decryption algorithms employed by the KindleForPC application. This
is a stand alone app that does not require you to determine a PID on
your own, and it does not need to run KindleForPC in order to extract
any data from memory.
Shoutz: The DarkReverser - thanks for mobidedrm! The last part of this
is just a C port of mobidedrm.
labba and I<3cabbages for motivating me to do this the right way.
You guys shouldn't need to spend all your time responding to all the
changes Amazon is going to force you to make in unswindle each time
the release a new version.
CMBDTC - nice work on the topaz break!
Lawrence Lessig - You are my hero. 'Nuff said.
Cory Doctorow - A voice of reason in a sea of insanity
Thumbs down: Disney, MPAA, RIAA - you guys suck. Making billions off
of the exploitation of works out of copyright while vigourously
pushing copyright extension to prevent others from doing the same
is the height of hypocrasy.
Congress - you guys suck too. Why you arrogant pricks think you
are smarter than the founding fathers is beyond me.
*/
Rationale:
Need a tool to enable fair use of purchased ebook content.
Need a tool that is not dependent on any particular version of
KindleForPC and that does not need to run KindleForPC in order to
extract a PID. The tool documents the structure of the kindle.info
file and the data and algorthims that are used to derive per book
PID values.
Installing:
A compiled binary is included. Though it was built using cygwin, it
should not require a cygwin installation in order to run it. To build
from source, you will need cygwin with gcc and make.
This has not been tested with Visual Studio, though you may be able to
pile all the files into a project and add the Crypt32.lib, Advapi32 and
zlib1 dependencies to build it.
usage: ./skindle [-d] [-v] -i <ebook file> -o <output file> [-k kindle.info file] [-p pid]
-d optional, for topaz files only, produce a decompressed output file
-i required name of the input mobi or topaz file
-o required name of the output file to generate
-k optional kindle.info path
-v dump the contents of kindle.info
-p additional PID values to attempt (can specifiy multiple times)
You only need to specify a kindle.info path if skindle can't find
your kindle.info file automatically

View file

@ -1,80 +0,0 @@
/*********************************************************************\
LICENCE: Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
VERSION HISTORY:
Bob Trower 08/04/01 -- Create Version 0.00.00B
\******************************************************************* */
#include <stdlib.h>
/*
** Translation Table as described in RFC1113
*/
static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*
** encodeblock
**
** encode 3 8-bit binary bytes as 4 '6-bit' characters
*/
void encodeblock(unsigned char in[3], unsigned char out[4], int len) {
out[0] = cb64[ in[0] >> 2 ];
out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
}
/*
** encode
**
** base64 encode a stream adding padding and line breaks as per spec.
*/
unsigned int base64(unsigned char *inbuf, unsigned int len, unsigned char *outbuf) {
unsigned char in[3], out[4];
int c;
unsigned int i = 0;
unsigned int outlen = 0;
while (i < len) {
int n = 0;
for(c = 0; c < 3; c++, i++) {
if (i < len) {
in[c] = inbuf[i];
n++;
}
else {
in[c] = 0;
}
}
if (n) {
encodeblock(in, out, n);
for(c = 0; c < 4; c++) {
outbuf[outlen++] = out[c];
}
}
}
return outlen;
}

View file

@ -1,85 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdlib.h>
#include <string.h>
#include "cbuf.h"
cbuf *b_new(unsigned int size) {
cbuf *b = (cbuf*)calloc(sizeof(cbuf), 1);
if (b) {
b->buf = (unsigned char *)malloc(size);
b->size = b->buf ? size : 0;
}
return b;
}
void b_free(cbuf *b) {
if (b) {
free(b->buf);
free(b);
}
}
void b_add_byte(cbuf *b, unsigned char ch) {
if (b == NULL) return;
if (b->idx == b->size) {
unsigned char *p = realloc(b->buf, b->size * 2);
if (p) {
b->buf = p;
b->size = b->size * 2;
}
}
if (b->idx < b->size) {
b->buf[b->idx++] = ch;
}
}
void b_add_buf(cbuf *b, unsigned char *buf, unsigned int len) {
if (b == NULL) return;
unsigned int new_sz = b->idx + len;
while (b->size < new_sz) {
unsigned char *p = realloc(b->buf, b->size * 2);
if (p) {
b->buf = p;
b->size = b->size * 2;
}
else break;
}
if ((b->idx + len) <= b->size) {
memcpy(b->buf + b->idx, buf, len);
b->idx += len;
}
}
void b_add_str(cbuf *b, const char *buf) {
if (b == NULL) return;
unsigned int len = strlen(buf);
unsigned int new_sz = b->idx + len;
while (b->size < new_sz) {
unsigned char *p = realloc(b->buf, b->size * 2);
if (p) {
b->buf = p;
b->size = b->size * 2;
}
else break;
}
if ((b->idx + len) <= b->size) {
memcpy(b->buf + b->idx, buf, len);
b->idx += len;
}
}

View file

@ -1,32 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef __CBUF_H
#define __CBUF_H
typedef struct _cbuf {
unsigned int size; //current size
unsigned int idx; //current position
unsigned char *buf;
} cbuf;
cbuf *b_new(unsigned int size);
void b_free(cbuf *b);
void b_add_byte(cbuf *b, unsigned char ch);
void b_add_buf(cbuf *b, unsigned char *buf, unsigned int len);
void b_add_str(cbuf *b, const char *buf);
#endif

View file

@ -1,381 +0,0 @@
/*
Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.c is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
either statically or dynamically; added missing #include <string.h>
in library.
2002-03-11 lpd Corrected argument list for main(), and added int return
type, in test program and T value program.
2002-02-21 lpd Added missing #include <stdio.h> in test program.
2000-07-03 lpd Patched to eliminate warnings about "constant is
unsigned in ANSI C, signed in traditional"; made test program
self-checking.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
1999-05-03 lpd Original version.
*/
#include "md5.h"
#include <string.h>
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
#ifdef ARCH_IS_BIG_ENDIAN
# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
#else
# define BYTE_ORDER 0
#endif
#define T_MASK ((md5_word_t)~0)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3 0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6 0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9 0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13 0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16 0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19 0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22 0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25 0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28 0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31 0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35 0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38 0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41 0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44 0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47 0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50 0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53 0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57 0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60 0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63 0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
static void
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
{
md5_word_t
a = pms->abcd[0], b = pms->abcd[1],
c = pms->abcd[2], d = pms->abcd[3];
md5_word_t t;
#if BYTE_ORDER > 0
/* Define storage only for big-endian CPUs. */
md5_word_t X[16];
#else
/* Define storage for little-endian or both types of CPUs. */
md5_word_t xbuf[16];
const md5_word_t *X;
#endif
{
#if BYTE_ORDER == 0
/*
* Determine dynamically whether this is a big-endian or
* little-endian machine, since we can use a more efficient
* algorithm on the latter.
*/
static const int w = 1;
if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
#endif
#if BYTE_ORDER <= 0 /* little-endian */
{
/*
* On little-endian machines, we can process properly aligned
* data without copying it.
*/
if (!((data - (const md5_byte_t *)0) & 3)) {
/* data are properly aligned */
X = (const md5_word_t *)data;
} else {
/* not aligned */
memcpy(xbuf, data, 64);
X = xbuf;
}
}
#endif
#if BYTE_ORDER == 0
else /* dynamic big-endian */
#endif
#if BYTE_ORDER >= 0 /* big-endian */
{
/*
* On big-endian machines, we must arrange the bytes in the
* right order.
*/
const md5_byte_t *xp = data;
int i;
# if BYTE_ORDER == 0
X = xbuf; /* (dynamic only) */
# else
# define xbuf X /* (static only) */
# endif
for (i = 0; i < 16; ++i, xp += 4)
xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
}
#endif
}
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
/* Round 1. */
/* Let [abcd k s i] denote the operation
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + F(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 7, T1);
SET(d, a, b, c, 1, 12, T2);
SET(c, d, a, b, 2, 17, T3);
SET(b, c, d, a, 3, 22, T4);
SET(a, b, c, d, 4, 7, T5);
SET(d, a, b, c, 5, 12, T6);
SET(c, d, a, b, 6, 17, T7);
SET(b, c, d, a, 7, 22, T8);
SET(a, b, c, d, 8, 7, T9);
SET(d, a, b, c, 9, 12, T10);
SET(c, d, a, b, 10, 17, T11);
SET(b, c, d, a, 11, 22, T12);
SET(a, b, c, d, 12, 7, T13);
SET(d, a, b, c, 13, 12, T14);
SET(c, d, a, b, 14, 17, T15);
SET(b, c, d, a, 15, 22, T16);
#undef SET
/* Round 2. */
/* Let [abcd k s i] denote the operation
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + G(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 1, 5, T17);
SET(d, a, b, c, 6, 9, T18);
SET(c, d, a, b, 11, 14, T19);
SET(b, c, d, a, 0, 20, T20);
SET(a, b, c, d, 5, 5, T21);
SET(d, a, b, c, 10, 9, T22);
SET(c, d, a, b, 15, 14, T23);
SET(b, c, d, a, 4, 20, T24);
SET(a, b, c, d, 9, 5, T25);
SET(d, a, b, c, 14, 9, T26);
SET(c, d, a, b, 3, 14, T27);
SET(b, c, d, a, 8, 20, T28);
SET(a, b, c, d, 13, 5, T29);
SET(d, a, b, c, 2, 9, T30);
SET(c, d, a, b, 7, 14, T31);
SET(b, c, d, a, 12, 20, T32);
#undef SET
/* Round 3. */
/* Let [abcd k s t] denote the operation
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)\
t = a + H(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 5, 4, T33);
SET(d, a, b, c, 8, 11, T34);
SET(c, d, a, b, 11, 16, T35);
SET(b, c, d, a, 14, 23, T36);
SET(a, b, c, d, 1, 4, T37);
SET(d, a, b, c, 4, 11, T38);
SET(c, d, a, b, 7, 16, T39);
SET(b, c, d, a, 10, 23, T40);
SET(a, b, c, d, 13, 4, T41);
SET(d, a, b, c, 0, 11, T42);
SET(c, d, a, b, 3, 16, T43);
SET(b, c, d, a, 6, 23, T44);
SET(a, b, c, d, 9, 4, T45);
SET(d, a, b, c, 12, 11, T46);
SET(c, d, a, b, 15, 16, T47);
SET(b, c, d, a, 2, 23, T48);
#undef SET
/* Round 4. */
/* Let [abcd k s t] denote the operation
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + I(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 6, T49);
SET(d, a, b, c, 7, 10, T50);
SET(c, d, a, b, 14, 15, T51);
SET(b, c, d, a, 5, 21, T52);
SET(a, b, c, d, 12, 6, T53);
SET(d, a, b, c, 3, 10, T54);
SET(c, d, a, b, 10, 15, T55);
SET(b, c, d, a, 1, 21, T56);
SET(a, b, c, d, 8, 6, T57);
SET(d, a, b, c, 15, 10, T58);
SET(c, d, a, b, 6, 15, T59);
SET(b, c, d, a, 13, 21, T60);
SET(a, b, c, d, 4, 6, T61);
SET(d, a, b, c, 11, 10, T62);
SET(c, d, a, b, 2, 15, T63);
SET(b, c, d, a, 9, 21, T64);
#undef SET
/* Then perform the following additions. (That is increment each
of the four registers by the value it had before this block
was started.) */
pms->abcd[0] += a;
pms->abcd[1] += b;
pms->abcd[2] += c;
pms->abcd[3] += d;
}
void
md5_init(md5_state_t *pms)
{
pms->count[0] = pms->count[1] = 0;
pms->abcd[0] = 0x67452301;
pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
pms->abcd[3] = 0x10325476;
}
void
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
{
const md5_byte_t *p = data;
int left = nbytes;
int offset = (pms->count[0] >> 3) & 63;
md5_word_t nbits = (md5_word_t)(nbytes << 3);
if (nbytes <= 0)
return;
/* Update the message length. */
pms->count[1] += nbytes >> 29;
pms->count[0] += nbits;
if (pms->count[0] < nbits)
pms->count[1]++;
/* Process an initial partial block. */
if (offset) {
int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
memcpy(pms->buf + offset, p, copy);
if (offset + copy < 64)
return;
p += copy;
left -= copy;
md5_process(pms, pms->buf);
}
/* Process full blocks. */
for (; left >= 64; p += 64, left -= 64)
md5_process(pms, p);
/* Process a final partial block. */
if (left)
memcpy(pms->buf, p, left);
}
void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
{
static const md5_byte_t pad[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
md5_byte_t data[8];
int i;
/* Save the length before padding. */
for (i = 0; i < 8; ++i)
data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
/* Pad to 56 bytes mod 64. */
md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
/* Append the length. */
md5_append(pms, data, 8);
for (i = 0; i < 16; ++i)
digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}

View file

@ -1,91 +0,0 @@
/*
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.h is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Removed support for non-ANSI compilers; removed
references to Ghostscript; clarified derivation from RFC 1321;
now handles byte order either statically or dynamically.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
added conditionalization for C++ compilation from Martin
Purschke <purschke@bnl.gov>.
1999-05-03 lpd Original version.
*/
#ifndef md5_INCLUDED
# define md5_INCLUDED
/*
* This package supports both compile-time and run-time determination of CPU
* byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
* compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
* defined as non-zero, the code will be compiled to run only on big-endian
* CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
* run on either big- or little-endian CPUs, but will run slightly less
* efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
*/
typedef unsigned char md5_byte_t; /* 8-bit byte */
typedef unsigned int md5_word_t; /* 32-bit word */
/* Define the state of the MD5 Algorithm. */
typedef struct md5_state_s {
md5_word_t count[2]; /* message length in bits, lsw first */
md5_word_t abcd[4]; /* digest buffer */
md5_byte_t buf[64]; /* accumulate block */
} md5_state_t;
#ifdef __cplusplus
extern "C"
{
#endif
/* Initialize the algorithm. */
void md5_init(md5_state_t *pms);
/* Append a string to the message. */
void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
/* Finish the message and return the digest. */
void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
#ifdef __cplusplus
} /* end extern "C" */
#endif
#endif /* md5_INCLUDED */

View file

@ -1,365 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "mobi.h"
unsigned char *getExthData(MobiFile *book, unsigned int type, unsigned int *len) {
unsigned int i;
unsigned int exthRecords = bswap_l(book->exth->recordCount);
ExthRecHeader *erh = book->exth->records;
*len = 0;
for (i = 0; i < exthRecords; i++) {
unsigned int recType = bswap_l(erh->type);
unsigned int recLen = bswap_l(erh->len);
if (recLen < 8) {
fprintf(stderr, "Invalid exth record length %d\n", i);
return NULL;
}
if (recType == type) {
*len = recLen - 8;
return (unsigned char*)(erh + 1);
}
erh = (ExthRecHeader*)(recLen + (char*)erh);
}
return NULL;
}
void enumExthRecords(ExthHeader *eh) {
unsigned int exthRecords = bswap_l(eh->recordCount);
unsigned int i;
unsigned char *data;
ExthRecHeader *erh = eh->records;
for (i = 0; i < exthRecords; i++) {
unsigned int recType = bswap_l(erh->type);
unsigned int recLen = bswap_l(erh->len);
fprintf(stderr, "%d: type - %d, len %d\n", i, recType, recLen);
if (recLen < 8) {
fprintf(stderr, "Invalid exth record length %d\n", i);
return;
}
data = (unsigned char*)(erh + 1);
switch (recType) {
case 1: //drm_server_id
fprintf(stderr, "drm_server_id: %s\n", data);
break;
case 2: //drm_commerce_id
fprintf(stderr, "drm_commerce_id: %s\n", data);
break;
case 3: //drm_ebookbase_book_id
fprintf(stderr, "drm_ebookbase_book_id: %s\n", data);
break;
case 100: //author
fprintf(stderr, "author: %s\n", data);
break;
case 101: //publisher
fprintf(stderr, "publisher: %s\n", data);
break;
case 106: //publishingdate
fprintf(stderr, "publishingdate: %s\n", data);
break;
case 113: //asin
fprintf(stderr, "asin: %s\n", data);
break;
case 208: //book unique drm key
fprintf(stderr, "book drm key: %s\n", data);
break;
case 503: //updatedtitle
fprintf(stderr, "updatedtitle: %s\n", data);
break;
default:
break;
}
erh = (ExthRecHeader*)(recLen + (char*)erh);
}
}
//implementation of Pukall Cipher 1
unsigned char *PC1(unsigned char *key, unsigned int klen, unsigned char *src,
unsigned char *dest, unsigned int len, int decryption) {
unsigned int sum1 = 0;
unsigned int sum2 = 0;
unsigned int keyXorVal = 0;
unsigned short wkey[8];
unsigned int i;
if (klen != 16) {
fprintf(stderr, "Bad key length!\n");
return NULL;
}
for (i = 0; i < 8; i++) {
wkey[i] = (key[i * 2] << 8) | key[i * 2 + 1];
}
for (i = 0; i < len; i++) {
unsigned int temp1 = 0;
unsigned int byteXorVal = 0;
unsigned int j, curByte;
for (j = 0; j < 8; j++) {
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 = src[i];
if (!decryption) {
keyXorVal = curByte * 257;
}
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF;
if (decryption) {
keyXorVal = curByte * 257;
}
for (j = 0; j < 8; j++) {
wkey[j] ^= keyXorVal;
}
dest[i] = curByte;
}
return dest;
}
unsigned int getSizeOfTrailingDataEntry(unsigned char *ptr, unsigned int size) {
unsigned int bitpos = 0;
unsigned int result = 0;
if (size <= 0) {
return result;
}
while (1) {
unsigned int v = ptr[size - 1];
result |= (v & 0x7F) << bitpos;
bitpos += 7;
size -= 1;
if ((v & 0x80) != 0 || (bitpos >= 28) || (size == 0)) {
return result;
}
}
}
unsigned int getSizeOfTrailingDataEntries(unsigned char *ptr, unsigned int size, unsigned int flags) {
unsigned int num = 0;
unsigned int testflags = flags >> 1;
while (testflags) {
if (testflags & 1) {
num += getSizeOfTrailingDataEntry(ptr, size - num);
}
testflags >>= 1;
}
if (flags & 1) {
num += (ptr[size - num - 1] & 0x3) + 1;
}
return num;
}
unsigned char *parseDRM(unsigned char *data, unsigned int count, unsigned char *pid, unsigned int pidlen) {
unsigned int i;
unsigned char temp_key_sum = 0;
unsigned char *found_key = NULL;
unsigned char *keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96";
unsigned char temp_key[16];
memset(temp_key, 0, 16);
memcpy(temp_key, pid, 8);
PC1(keyvec1, 16, temp_key, temp_key, 16, 0);
for (i = 0; i < 16; i++) {
temp_key_sum += temp_key[i];
}
for (i = 0; i < count; i++) {
unsigned char kk[32];
vstruct *v = (vstruct*)(data + i * 0x30);
kstruct *k = (kstruct*)PC1(temp_key, 16, v->cookie, kk, 32, 1);
if (v->verification == k->ver && v->cksum[0] == temp_key_sum &&
(bswap_l(k->flags) & 0x1F) == 1) {
found_key = (unsigned char*)malloc(16);
memcpy(found_key, k->finalkey, 16);
break;
}
}
return found_key;
}
void freeMobiFile(MobiFile *book) {
free(book->hr);
free(book->record0);
free(book);
}
MobiFile *parseMobiHeader(FILE *f) {
unsigned int numRecs, i, magic;
MobiFile *book = (MobiFile*)calloc(sizeof(MobiFile), 1);
book->f = f;
if (fread(&book->pdb, sizeof(PDB), 1, f) != 1) {
fprintf(stderr, "Failed to read Palm headers\n");
free(book);
return NULL;
}
//do BOOKMOBI check
if (book->pdb.type != 0x4b4f4f42 || book->pdb.creator != 0x49424f4d) {
fprintf(stderr, "Invalid header type or creator\n");
free(book);
return NULL;
}
book->recs = bswap_s(book->pdb.numRecs);
book->hr = (HeaderRec*)malloc(book->recs * sizeof(HeaderRec));
if (fread(book->hr, sizeof(HeaderRec), book->recs, f) != book->recs) {
fprintf(stderr, "Failed read of header record\n");
freeMobiFile(book);
return NULL;
}
book->record0_offset = bswap_l(book->hr[0].offset);
book->record0_size = bswap_l(book->hr[1].offset) - book->record0_offset;
if (fseek(f, book->record0_offset, SEEK_SET) == -1) {
fprintf(stderr, "bad seek to header record offset\n");
freeMobiFile(book);
return NULL;
}
book->record0 = (unsigned char*)malloc(book->record0_size);
if (fread(book->record0, book->record0_size, 1, f) != 1) {
fprintf(stderr, "bad read of record0\n");
freeMobiFile(book);
return NULL;
}
book->pdh = (PalmDocHeader*)(book->record0);
if (bswap_s(book->pdh->encryptionType) != 2) {
fprintf(stderr, "MOBI BOOK is not encrypted\n");
freeMobiFile(book);
return NULL;
}
book->textRecs = bswap_s(book->pdh->recordCount);
book->mobi = (MobiHeader*)(book->pdh + 1);
if (book->mobi->id != 0x49424f4d) {
fprintf(stderr, "MOBI header not found\n");
freeMobiFile(book);
return NULL;
}
book->mobiLen = bswap_l(book->mobi->hdrLen);
book->extra_data_flags = 0;
if (book->mobiLen >= 0xe4) {
book->extra_data_flags = bswap_s(book->mobi->extra_flags);
}
if ((bswap_l(book->mobi->exthFlags) & 0x40) == 0) {
fprintf(stderr, "Missing exth header\n");
freeMobiFile(book);
return NULL;
}
book->exth = (ExthHeader*)(book->mobiLen + (char*)(book->mobi));
if (book->exth->id != 0x48545845) {
fprintf(stderr, "EXTH header not found\n");
freeMobiFile(book);
return NULL;
}
//if you want a list of EXTH records, uncomment the following
// enumExthRecords(exth);
book->drmCount = bswap_l(book->mobi->drmCount);
if (book->drmCount == 0) {
fprintf(stderr, "no PIDs found in this file\n");
freeMobiFile(book);
return NULL;
}
return book;
}
int writeMobiOutputFile(MobiFile *book, FILE *out, unsigned char *key,
unsigned int drmOffset, unsigned int drm_len) {
int i;
struct stat statbuf;
fstat(fileno(book->f), &statbuf);
// kill the drm keys
memset(book->record0 + drmOffset, 0, drm_len);
// kill the drm pointers
book->mobi->drmOffset = 0xffffffff;
book->mobi->drmCount = book->mobi->drmSize = book->mobi->drmFlags = 0;
// clear the crypto type
book->pdh->encryptionType = 0;
fwrite(&book->pdb, sizeof(PDB), 1, out);
fwrite(book->hr, sizeof(HeaderRec), book->recs, out);
fwrite("\x00\x00", 1, 2, out);
fwrite(book->record0, book->record0_size, 1, out);
//need to zero out exth 209 data
for (i = 1; i < book->recs; i++) {
unsigned int offset = bswap_l(book->hr[i].offset);
unsigned int len, extra_size = 0;
unsigned char *rec;
if (i == (book->recs - 1)) { //last record extends to end of file
len = statbuf.st_size - offset;
}
else {
len = bswap_l(book->hr[i + 1].offset) - offset;
}
//make sure we are at proper offset
while (ftell(out) < offset) {
fwrite("\x00", 1, 1, out);
}
rec = (unsigned char *)malloc(len);
if (fseek(book->f, offset, SEEK_SET) != 0) {
fprintf(stderr, "Failed record seek on input\n");
freeMobiFile(book);
free(rec);
return 0;
}
if (fread(rec, len, 1, book->f) != 1) {
fprintf(stderr, "Failed record read on input\n");
freeMobiFile(book);
free(rec);
return 0;
}
if (i <= book->textRecs) { //decrypt if necessary
extra_size = getSizeOfTrailingDataEntries(rec, len, book->extra_data_flags);
PC1(key, 16, rec, rec, len - extra_size, 1);
}
fwrite(rec, len, 1, out);
free(rec);
}
return 1;
}

View file

@ -1,147 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef __MOBI_H
#define __MOBI_H
#include <stdio.h>
#include "skinutils.h"
#pragma pack(2)
typedef struct _PDB {
char name[32];
unsigned short attrib;
unsigned short version;
unsigned int created;
unsigned int modified;
unsigned int backup;
unsigned int modNum;
unsigned int appInfoID;
unsigned int sortInfoID;
unsigned int type;
unsigned int creator;
unsigned int uniqueIDseed;
unsigned int nextRecordListID;
unsigned short numRecs;
} PDB;
typedef struct _HeaderRec {
unsigned int offset;
unsigned int attribId;
} HeaderRec;
#define attrib(x) ((x)&0xFF)
#define id(x) (bswap_l((x) & 0xFFFFFF00))
typedef struct _PalmDocHeader {
unsigned short compression;
unsigned short reserverd1;
unsigned int textLength;
unsigned short recordCount;
unsigned short recordSize;
unsigned short encryptionType;
unsigned short reserved2;
} PalmDocHeader;
//checked lengths are 24, 116, 208, 228
typedef struct _MobiHeader {
unsigned int id;
unsigned int hdrLen;
unsigned int type;
unsigned int encoding;
unsigned int uniqueId;
unsigned int generator;
unsigned char reserved1[40];
unsigned int firstNonBookIdx;
unsigned int nameOffset;
unsigned int nameLength;
unsigned int language;
unsigned int inputLang;
unsigned int outputLang;
unsigned int formatVersion;
unsigned int firstImageIdx;
unsigned char unknown1[16];
unsigned int exthFlags;
unsigned char unknown2[36];
unsigned int drmOffset;
unsigned int drmCount;
unsigned int drmSize;
unsigned int drmFlags;
unsigned char unknown3[58];
unsigned short extra_flags;
} MobiHeader;
typedef struct _ExthRecHeader {
unsigned int type;
unsigned int len;
} ExthRecHeader;
typedef struct _ExthHeader {
unsigned int id;
unsigned int hdrLen;
unsigned int recordCount;
ExthRecHeader records[1];
} ExthHeader;
typedef struct _vstruct {
unsigned int verification;
unsigned int size;
unsigned int type;
unsigned char cksum[4];
unsigned char cookie[32];
} vstruct;
typedef struct _kstruct {
unsigned int ver;
unsigned int flags;
unsigned char finalkey[16];
unsigned int expiry;
unsigned int expiry2;
} kstruct;
typedef struct _MobiFile {
FILE *f;
PDB pdb;
HeaderRec *hr;
PalmDocHeader *pdh;
MobiHeader *mobi;
ExthHeader *exth;
unsigned char *record0;
unsigned int record0_offset;
unsigned int record0_size;
unsigned int mobiLen;
unsigned int extra_data_flags;
unsigned int recs;
unsigned int drmCount;
unsigned int textRecs;
PidList *pids; //extra pids to try from command line
} MobiFile;
unsigned char *getExthData(MobiFile *book, unsigned int type, unsigned int *len);
void enumExthRecords(ExthHeader *eh);
unsigned char *PC1(unsigned char *key, unsigned int klen, unsigned char *src,
unsigned char *dest, unsigned int len, int decryption);
unsigned int getSizeOfTrailingDataEntry(unsigned char *ptr, unsigned int size);
unsigned int getSizeOfTrailingDataEntries(unsigned char *ptr, unsigned int size, unsigned int flags);
unsigned char *parseDRM(unsigned char *data, unsigned int count, unsigned char *pid, unsigned int pidlen);
void freeMobiFile(MobiFile *book);
MobiFile *parseMobiHeader(FILE *f);
int writeMobiOutputFile(MobiFile *book, FILE *out, unsigned char *key,
unsigned int drmOffset, unsigned int drm_len);
#endif

View file

@ -1,234 +0,0 @@
/*
sha1.c: Implementation of SHA-1 Secure Hash Algorithm-1
Based upon: NIST FIPS180-1 Secure Hash Algorithm-1
http://www.itl.nist.gov/fipspubs/fip180-1.htm
Non-official Japanese Translation by HIRATA Yasuyuki:
http://yasu.asuka.net/translations/SHA-1.html
Copyright (C) 2002 vi@nwr.jp. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgement in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as beging the original software.
3. This notice may not be removed or altered from any source distribution.
Note:
The copyright notice above is copied from md5.h by L. Peter Deutsch
<ghost@aladdin.com>. Thank him since I'm not a good speaker of English. :)
*/
#include <string.h>
#include "sha1.h"
/*
* Packing bytes to a word
*
* Should not assume p is aligned to word boundary
*/
static sha1_word_t packup(sha1_byte_t *p)
{
/* Portable, but slow */
return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3] << 0;
}
/*
* Unpacking a word to bytes
*
* Should not assume p is aligned to word boundary
*/
static void unpackup(sha1_byte_t *p, sha1_word_t q)
{
p[0] = (q >> 24) & 0xff;
p[1] = (q >> 16) & 0xff;
p[2] = (q >> 8) & 0xff;
p[3] = (q >> 0) & 0xff;
}
/*
* Processing a block
*/
static void sha1_update_now(sha1_state_s *pms, sha1_byte_t *bp)
{
sha1_word_t tmp, a, b, c, d, e, w[16+16];
int i, s;
/* pack 64 bytes into 16 words */
for(i = 0; i < 16; i++) {
w[i] = packup(bp + i * sizeof(sha1_word_t));
}
memcpy(w + 16, w + 0, sizeof(sha1_word_t) * 16);
a = pms->sha1_h[0], b = pms->sha1_h[1], c = pms->sha1_h[2], d = pms->sha1_h[3], e = pms->sha1_h[4];
#define rot(x,n) (((x) << n) | ((x) >> (32-n)))
#define f0(b, c, d) ((b&c)|(~b&d))
#define f1(b, c, d) (b^c^d)
#define f2(b, c, d) ((b&c)|(b&d)|(c&d))
#define f3(b, c, d) (b^c^d)
#define k0 0x5a827999
#define k1 0x6ed9eba1
#define k2 0x8f1bbcdc
#define k3 0xca62c1d6
/* t=0-15 */
s = 0;
for(i = 0; i < 16; i++) {
tmp = rot(a, 5) + f0(b, c, d) + e + w[s] + k0;
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
s = (s + 1) % 16;
}
/* t=16-19 */
for(i = 16; i < 20; i++) {
w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1);
w[s+16] = w[s];
tmp = rot(a, 5) + f0(b, c, d) + e + w[s] + k0;
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
s = (s + 1) % 16;
}
/* t=20-39 */
for(i = 0; i < 20; i++) {
w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1);
w[s+16] = w[s];
tmp = rot(a, 5) + f1(b, c, d) + e + w[s] + k1;
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
s = (s + 1) % 16;
}
/* t=40-59 */
for(i = 0; i < 20; i++) {
w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1);
w[s+16] = w[s];
tmp = rot(a, 5) + f2(b, c, d) + e + w[s] + k2;
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
s = (s + 1) % 16;
}
/* t=60-79 */
for(i = 0; i < 20; i++) {
w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1);
w[s+16] = w[s];
tmp = rot(a, 5) + f3(b, c, d) + e + w[s] + k3;
e = d; d = c; c = rot(b, 30); b = a; a = tmp;
s = (s + 1) % 16;
}
pms->sha1_h[0] += a, pms->sha1_h[1] += b, pms->sha1_h[2] += c, pms->sha1_h[3] += d, pms->sha1_h[4] += e;
}
/*
* Increment sha1_size1, sha1_size2 field of sha1_state_s
*/
static void incr(sha1_state_s *pms, int v)
{
sha1_word_t q;
q = pms->sha1_size1 + v * BITS;
if(q < pms->sha1_size1) {
pms->sha1_size2++;
}
pms->sha1_size1 = q;
}
/*
* Initialize sha1_state_s as FIPS specifies
*/
void sha1_init(sha1_state_s *pms)
{
memset(pms, 0, sizeof(*pms));
pms->sha1_h[0] = 0x67452301; /* Initialize H[0]-H[4] */
pms->sha1_h[1] = 0xEFCDAB89;
pms->sha1_h[2] = 0x98BADCFE;
pms->sha1_h[3] = 0x10325476;
pms->sha1_h[4] = 0xC3D2E1F0;
}
/*
* Fill block and update output when needed
*/
void sha1_update(sha1_state_s *pms, sha1_byte_t *bufp, int length)
{
/* Is the buffer partially filled? */
if(pms->sha1_count != 0) {
if(pms->sha1_count + length >= (signed) sizeof(pms->sha1_buf)) { /* buffer is filled enough */
int fil = sizeof(pms->sha1_buf) - pms->sha1_count; /* length to copy */
memcpy(pms->sha1_buf + pms->sha1_count, bufp, fil);
sha1_update_now(pms, pms->sha1_buf);
length -= fil;
bufp += fil;
pms->sha1_count = 0;
incr(pms, fil);
} else {
memcpy(pms->sha1_buf + pms->sha1_count, bufp, length);
pms->sha1_count += length;
incr(pms, length);
return;
}
}
/* Loop to update state */
for(;;) {
if(length < (signed) sizeof(pms->sha1_buf)) { /* Short to fill up the buffer */
if(length) {
memcpy(pms->sha1_buf, bufp, length);
}
pms->sha1_count = length;
incr(pms, length);
break;
}
sha1_update_now(pms, bufp);
length -= sizeof(pms->sha1_buf);
bufp += sizeof(pms->sha1_buf);
incr(pms, sizeof(pms->sha1_buf));
}
}
void sha1_finish(sha1_state_s *pms, sha1_byte_t output[SHA1_OUTPUT_SIZE])
{
int i;
sha1_byte_t buf[1];
/* fill a bit */
buf[0] = 0x80;
sha1_update(pms, buf, 1);
/* Decrement sha1_size1, sha1_size2 */
if((pms->sha1_size1 -= BITS) == 0) {
pms->sha1_size2--;
}
/* fill zeros */
if(pms->sha1_count > (signed) (sizeof(pms->sha1_buf) - 2 * sizeof(sha1_word_t))) {
memset(pms->sha1_buf + pms->sha1_count, 0, sizeof(pms->sha1_buf) - pms->sha1_count);
sha1_update_now(pms, pms->sha1_buf);
pms->sha1_count = 0;
}
memset(pms->sha1_buf + pms->sha1_count, 0,
sizeof(pms->sha1_buf) - pms->sha1_count - sizeof(sha1_word_t) * 2);
/* fill last length */
unpackup(pms->sha1_buf + sizeof(pms->sha1_buf) - sizeof(sha1_word_t) * 2, pms->sha1_size2);
unpackup(pms->sha1_buf + sizeof(pms->sha1_buf) - sizeof(sha1_word_t) * 1, pms->sha1_size1);
/* final update */
sha1_update_now(pms, pms->sha1_buf);
/* move hash value to output byte array */
for(i = 0; i < (signed) (sizeof(pms->sha1_h)/sizeof(sha1_word_t)); i++) {
unpackup(output + i * sizeof(sha1_word_t), pms->sha1_h[i]);
}
}

View file

@ -1,68 +0,0 @@
/*
sha1.h: Implementation of SHA-1 Secure Hash Algorithm-1
Based upon: NIST FIPS180-1 Secure Hash Algorithm-1
http://www.itl.nist.gov/fipspubs/fip180-1.htm
Non-official Japanese Translation by HIRATA Yasuyuki:
http://yasu.asuka.net/translations/SHA-1.html
Copyright (C) 2002 vi@nwr.jp. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgement in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as beging the original software.
3. This notice may not be removed or altered from any source distribution.
Note:
The copyright notice above is copied from md5.h by L. Petet Deutsch
<ghost@aladdin.com>. Thank him since I'm not a good speaker of English. :)
*/
#ifndef SHA1_H
#define SHA1_H
typedef unsigned int sha1_word_t; /* 32bits unsigned integer */
typedef unsigned char sha1_byte_t; /* 8bits unsigned integer */
#define BITS 8
/* Define the state of SHA-1 algorithm */
typedef struct {
sha1_byte_t sha1_buf[64]; /* 512 bits */
int sha1_count; /* How many bytes are used */
sha1_word_t sha1_size1; /* Length counter Lower Word */
sha1_word_t sha1_size2; /* Length counter Upper Word */
sha1_word_t sha1_h[5]; /* Hash output */
} sha1_state_s;
#define SHA1_OUTPUT_SIZE 20 /* in bytes */
/* External Functions */
#ifdef __cplusplus
extern "C" {
#endif
/* Initialize SHA-1 algorithm */
void sha1_init(sha1_state_s *pms);
/* Append a string to SHA-1 algorithm */
void sha1_update(sha1_state_s *pms, sha1_byte_t *input_buffer, int length);
/* Finish the SHA-1 algorithm and return the hash */
void sha1_finish(sha1_state_s *pms, sha1_byte_t output[SHA1_OUTPUT_SIZE]);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -1,461 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* Dependencies: none
* build on cygwin:
* gcc -o skindle skindle.c md5.c sha1.c b64.c -lCrypt32
* or gcc -o skindle skindle.c md5.c sha1.c b64.c -lCrypt32 -mno-cygwin
* Under cygwin, you can just type make to build it.
* The code should compile with Visual Studio, just add all the files to
* a project and add the Crypt32.lib dependency and it should build as a
* Win32 console app.
*/
/*
* MUST be run on the computer on which KindleForPC is installed
* under the account that was used to purchase DRM'ed content.
* Requires your kindle.info file which can be found in something like:
* <User home>\...\Amazon\Kindle For PC\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}
* where ... varies by platform but is "Local Settings\Application Data" on XP
*/
/*
What: KindleForPC DRM removal utility to preserve your fair use rights!
Why: Fair use is a well established doctrine, and I am no fan of vendor
lockin.
How: This utility implements the PID extraction, DRM key generation and
decryption algorithms employed by the KindleForPC application. This
is a stand alone app that does not require you to determine a PID on
your own, and it does not need to run KindleForPC in order to extract
any data from memory.
Shoutz: The DarkReverser - thanks for mobidedrm! The last part of this
is just a C port of mobidedrm.
labba and I<3cabbages for motivating me to do this the right way.
You guys shouldn't need to spend all your time responding to all the
changes Amazon is going to force you to make in unswindle each time
the release a new version.
Lawrence Lessig - You are my hero. 'Nuff said.
Thumbs down: Disney, MPAA, RIAA - you guys suck. Making billions off
of the exploitation of works out of copyright while vigourously
pushing copyright extension to prevent others from doing the same
is the height of hypocrasy.
Congress - you guys suck too. Why you arrogant pricks think you
are smarter than the founding fathers is beyond me.
*/
#include <windows.h>
#include <Wincrypt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "skinutils.h"
#include "cbuf.h"
#include "mobi.h"
#include "tpz.h"
#include "zlib.h"
int processTopaz(FILE *in, char *outFile, int explode, PidList *extraPids) {
//had to pile all these up here to please VS2009
cbuf *tpzHeaders, *tpzBody;
struct stat statbuf;
FILE *out;
unsigned int i;
char *keysRecord, *keysRecordRecord;
TopazFile *topaz;
char *pid;
fstat(fileno(in), &statbuf);
topaz = parseTopazHeader(in);
if (topaz == NULL) {
fprintf(stderr, "Failed to parse topaz headers\n");
return 0;
}
topaz->pids = extraPids;
tpzHeaders = b_new(topaz->bodyOffset);
tpzBody = b_new(statbuf.st_size);
parseMetadata(topaz);
// dumpMap(bookMetadata);
keysRecord = getMetadata(topaz, "keys");
if (keysRecord == NULL) {
//fail
}
keysRecordRecord = getMetadata(topaz, keysRecord);
if (keysRecordRecord == NULL) {
//fail
}
pid = getBookPid(keysRecord, strlen(keysRecord), keysRecordRecord, strlen(keysRecordRecord));
if (pid == NULL) {
fprintf(stderr, "Failed to extract pid automatically\n");
}
else {
char *title = getMetadata(topaz, "Title");
fprintf(stderr, "PID for %s is: %s\n", title ? title : "UNK", pid);
}
/*
unique pid is computed as:
base64(sha1(idArray . kindleToken . 209_data . 209_tokens))
*/
//
// Decrypt book key
//
Payload *dkey = getBookPayloadRecord(topaz, "dkey", 0, 0);
if (dkey == NULL) {
fprintf(stderr, "No dkey record found\n");
freeTopazFile(topaz);
return 0;
}
if (pid) {
topaz->bookKey = decryptDkeyRecords(dkey, pid);
free(pid);
}
if (topaz->bookKey == NULL) {
if (extraPids) {
int p;
freePayload(dkey);
for (p = 0; p < extraPids->numPids; p++) {
dkey = getBookPayloadRecord(topaz, "dkey", 0, 0);
topaz->bookKey = decryptDkeyRecords(dkey, extraPids->pidList[p]);
if (topaz->bookKey) break;
}
}
if (topaz->bookKey == NULL) {
fprintf(stderr, "No valid pids available, failed to find DRM key\n");
freeTopazFile(topaz);
freePayload(dkey);
return 0;
}
}
fprintf(stderr, "Found a DRM key!\n");
for (i = 0; i < 8; i++) {
fprintf(stderr, "%02x", topaz->bookKey[i]);
}
fprintf(stderr, "\n");
out = fopen(outFile, "wb");
if (out == NULL) {
fprintf(stderr, "Failed to open output file, quitting\n");
freeTopazFile(topaz);
freePayload(dkey);
return 0;
}
writeTopazOutputFile(topaz, out, tpzHeaders, tpzBody, explode);
fwrite(tpzHeaders->buf, tpzHeaders->idx, 1, out);
fwrite(tpzBody->buf, tpzBody->idx, 1, out);
fclose(out);
b_free(tpzHeaders);
b_free(tpzBody);
freePayload(dkey);
freeTopazFile(topaz);
return 1;
}
int processMobi(FILE *prc, char *outFile, PidList *extraPids) {
//had to pile all these up here to please VS2009
PDB header;
cbuf *keyBuf;
char *pid;
FILE *out;
unsigned int i, keyPtrLen;
unsigned char *keyPtr;
unsigned int drmOffset, drm_len;
unsigned char *drm, *found_key = NULL;
MobiFile *book;
int result;
book = parseMobiHeader(prc);
if (book == NULL) {
fprintf(stderr, "Failed to read mobi headers\n");
return 0;
}
book->pids = extraPids;
keyPtr = getExthData(book, 209, &keyPtrLen);
keyBuf = b_new(128);
if (keyPtr != NULL) {
unsigned int idx;
for (idx = 0; idx < keyPtrLen; idx += 5) {
unsigned char *rec;
unsigned int dlen;
unsigned int rtype = bswap_l(*(unsigned int*)(keyPtr + idx + 1));
rec = getExthData(book, rtype, &dlen);
if (rec != NULL) {
b_add_buf(keyBuf, rec, dlen);
}
}
}
pid = getBookPid(keyPtr, keyPtrLen, keyBuf->buf, keyBuf->idx);
b_free(keyBuf);
if (pid == NULL) {
fprintf(stderr, "Failed to extract pid automatically\n");
}
else {
fprintf(stderr, "PID for %s is: %s\n", book->pdb.name, pid);
}
/*
unique pid is computed as:
base64(sha1(idArray . kindleToken . 209_data . 209_tokens))
*/
drmOffset = bswap_l(book->mobi->drmOffset);
drm_len = bswap_l(book->mobi->drmSize);
drm = book->record0 + drmOffset;
if (pid) {
found_key = parseDRM(drm, book->drmCount, pid, 8);
free(pid);
}
if (found_key == NULL) {
if (extraPids) {
int p;
for (p = 0; p < extraPids->numPids; p++) {
found_key = parseDRM(drm, book->drmCount, extraPids->pidList[p], 8);
if (found_key) break;
}
}
if (found_key == NULL) {
fprintf(stderr, "No valid pids available, failed to find DRM key\n");
freeMobiFile(book);
return 0;
}
}
fprintf(stderr, "Found a DRM key!\n");
out = fopen(outFile, "wb");
if (out == NULL) {
fprintf(stderr, "Failed to open output file, quitting\n");
freeMobiFile(book);
free(found_key);
return 0;
}
result = writeMobiOutputFile(book, out, found_key, drmOffset, drm_len);
fclose(out);
if (result == 0) {
_unlink(outFile);
}
freeMobiFile(book);
free(found_key);
return result;
}
enum {
FileTypeUnk,
FileTypeMobi,
FileTypeTopaz
};
int getFileType(FILE *in) {
PDB p;
int type = FileTypeUnk;
fseek(in, 0, SEEK_SET);
fread(&p, sizeof(p), 1, in);
if (p.type == 0x4b4f4f42 && p.creator == 0x49424f4d) {
type = FileTypeMobi;
}
else if (strncmp(p.name, "TPZ0", 4) == 0) {
type = FileTypeTopaz;
}
fseek(in, 0, SEEK_SET);
return type;
}
void usage() {
fprintf(stderr, "usage: ./skindle [-d] [-v] -i <ebook file> -o <output file> [-k kindle.info file] [-p pid]\n");
fprintf(stderr, " -d optional, for topaz files only, produce a decompressed output file\n");
fprintf(stderr, " -i required name of the input mobi or topaz file\n");
fprintf(stderr, " -o required name of the output file to generate\n");
fprintf(stderr, " -k optional kindle.info path\n");
fprintf(stderr, " -v dump the contents of kindle.info\n");
fprintf(stderr, " -p additional PID values to attempt (can specifiy multiple times)\n");
}
extern char *optarg;
extern int optind;
int main(int argc, char **argv) {
//had to pile all these up here to please VS2009
FILE *in;
int type, explode = 0;
int result = 0;
int firstArg = 1;
int opt;
PidList *pids = NULL;
char *infile = NULL, *outfile = NULL, *kinfo = NULL;
int dump = 0;
while ((opt = getopt(argc, argv, "vdp:i:o:k:")) != -1) {
switch (opt) {
case 'v':
dump = 1;
break;
case 'd':
explode = 1;
break;
case 'p': {
int l = strlen(optarg);
if (l == 10) {
if (!verifyPidChecksum(optarg)) {
fprintf(stderr, "Invalid pid %s, skipping\n", optarg);
break;
}
optarg[8] = 0;
}
else if (l != 8) {
fprintf(stderr, "Invalid pid length for %s, skipping\n", optarg);
break;
}
if (pids == NULL) {
pids = (PidList*)malloc(sizeof(PidList));
pids->numPids = 1;
pids->pidList[0] = optarg;
}
else {
pids = (PidList*)realloc(pids, sizeof(PidList) + pids->numPids * sizeof(unsigned char*));
pids->pidList[pids->numPids++] = optarg;
}
break;
}
case 'k':
kinfo = optarg;
break;
case 'i':
infile = optarg;
break;
case 'o':
outfile = optarg;
break;
default: /* '?' */
usage();
exit(1);
}
}
if (optind != argc) {
fprintf(stderr, "Extra options ignored\n");
}
if (!buildKindleMap(kinfo)) {
fprintf(stderr, "buildKindleMap failed\n");
usage();
exit(1);
}
//The following loop dumps the contents of your kindle.info file
if (dump) {
MapList *ml;
// dumpKindleMap();
fprintf(stderr, "\nDumping kindle.info contents:\n");
for (ml = kindleMap; ml; ml = ml->next) {
DATA_BLOB DataIn;
DATA_BLOB DataOut;
DataIn.pbData = mazamaDecode(ml->value, (int*)&DataIn.cbData);
if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) {
fprintf(stderr, "%s ==> %s\n", ml->key, translateKindleKey(ml->key));
fwrite(DataOut.pbData, DataOut.cbData, 1, stderr);
fprintf(stderr, "\n\n");
LocalFree(DataOut.pbData);
}
else {
fprintf(stderr, "CryptUnprotectData failed\n");
}
free(DataIn.pbData);
}
}
if (infile == NULL && outfile == NULL) {
//special case, user just wants to see kindle.info
freeMap(kindleMap);
exit(1);
}
if (infile == NULL) {
fprintf(stderr, "Missing input file name\n");
usage();
freeMap(kindleMap);
exit(1);
}
if (outfile == NULL) {
fprintf(stderr, "Missing output file name\n");
usage();
freeMap(kindleMap);
exit(1);
}
in = fopen(infile, "rb");
if (in == NULL) {
fprintf(stderr, "%s bad open, quitting\n", infile);
freeMap(kindleMap);
exit(1);
}
type = getFileType(in);
if (type == FileTypeTopaz) {
result = processTopaz(in, outfile, explode, pids);
}
else if (type == FileTypeMobi) {
result = processMobi(in, outfile, pids);
}
else {
fprintf(stderr, "%s file type unknown, quitting\n", infile);
fclose(in);
freeMap(kindleMap);
exit(1);
}
fclose(in);
if (result) {
fprintf(stderr, "Success! Enjoy!\n");
}
else {
fprintf(stderr, "An error occurred, unable to process input file!\n");
}
freeMap(kindleMap);
return 0;
}

View file

@ -1,539 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <windows.h>
#include <Wincrypt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "skinutils.h"
/* The kindle.info file created when you install KindleForPC is a set
* of key:value pairs delimited by '{'. The keys and values are encoded
* in a variety of ways. Keys are the mazama64 encoded md5 hash of the
* key name, while values are the mazama64 encoding of the blob returned
* by the Windows CryptProtectData function. The use of CryptProtectData
* is what locks things to a particular user/machine
* kindle.info layout
* Key:AbaZZ6z4a7ZxzLzkZcaqauZMZjZ_Ztz6 ("kindle.account.tokens")
* Value: mazama64Encode(CryptProtectData(some sha1 hash))
* Key:AsAWa4ZJAQaCZ7A3zrZSaZavZMarZFAw ("kindle.cookie.item")
* Value: mazama64Encode(CryptProtectData(base64(144 bytes of data)))
* Key:ZHatAla4a-zTzWA-AvaeAvZQzKZ-agAz ("eulaVersionAccepted")
* Value: mazama64Encode(CryptProtectData(kindle version?))
* Key:ZiajZga7Z9zjZRz7AfZ-zRzUANZNZJzP ("login_date")
* Value: mazama64Encode(CryptProtectData(registration date))
* Key:ZkzeAUA-Z2ZYA2Z_ayA_ahZEATaEAOaG ("kindle.token.item")
* Value: mazama64Encode(CryptProtectData(multi-field crypto data))
* {enc:xxx}{iv:xxx}{key:xxx}{name:xxx}{serial:xxx}
* enc:base64(binary blob)
* iv:base64(16 bytes)
* key:base64(256 bytes)
* name:base64("ADPTokenEncryptionKey")
* serial:base64("1")
* Key:aVzrzRAFZ7aIzmASZOzVzIAGAKawzwaU ("login")
* Value: mazama64Encode(CryptProtectData(your amazon email))
* Key:avalzbzkAcAPAQA5ApZgaOZPzQZzaiaO mazama64Encode(md5("MazamaRandomNumber"))
* Value: mazama64Encode(CryptProtectData(mazama32Encode(32 bytes random data)))
* Key:zgACzqAjZ2zzAmAJa6ZFaZALaYAlZrz- ("kindle.key.item")
* Value: mazama64Encode(CryptProtectData(RSA private key)) no password
* Key:zga-aIANZPzbzfZ1zHZWZcA4afZMZcA_ ("kindle.name.info")
* Value: mazama64Encode(CryptProtectData(your name))
* Key:zlZ9afz1AfAVZjacaqa-ZHa1aIa_ajz7 ("kindle.device.info");
* Value: mazama64Encode(CryptProtectData(the name of your kindle))
*/
char *kindleKeys[] = {
"AbaZZ6z4a7ZxzLzkZcaqauZMZjZ_Ztz6", "kindle.account.tokens",
"AsAWa4ZJAQaCZ7A3zrZSaZavZMarZFAw", "kindle.cookie.item",
"ZHatAla4a-zTzWA-AvaeAvZQzKZ-agAz", "eulaVersionAccepted",
"ZiajZga7Z9zjZRz7AfZ-zRzUANZNZJzP", "login_date",
"ZkzeAUA-Z2ZYA2Z_ayA_ahZEATaEAOaG", "kindle.token.item",
"aVzrzRAFZ7aIzmASZOzVzIAGAKawzwaU", "login",
"avalzbzkAcAPAQA5ApZgaOZPzQZzaiaO", "MazamaRandomNumber",
"zgACzqAjZ2zzAmAJa6ZFaZALaYAlZrz-", "kindle.key.item",
"zga-aIANZPzbzfZ1zHZWZcA4afZMZcA_", "kindle.name.info",
"zlZ9afz1AfAVZjacaqa-ZHa1aIa_ajz7", "kindle.device.info"
};
MapList *kindleMap;
unsigned short bswap_s(unsigned short s) {
return (s >> 8) | (s << 8);
}
unsigned int bswap_l(unsigned int s) {
unsigned int u = bswap_s(s);
unsigned int l = bswap_s(s >> 16);
return (u << 16) | l;
}
char *translateKindleKey(char *key) {
int n = sizeof(kindleKeys) / sizeof(char*);
int i;
for (i = 0; i < n; i += 2) {
if (strcmp(key, kindleKeys[i]) == 0) {
return kindleKeys[i + 1];
}
}
return NULL;
}
MapList *findNode(MapList *map, char *key) {
MapList *l;
for (l = map; l; l = l->next) {
if (strcmp(key, l->key) == 0) {
return l;
}
}
return NULL;
}
MapList *findKindleNode(char *key) {
return findNode(kindleMap, key);
}
char *getNodeValue(MapList *map, char *key) {
MapList *l;
for (l = map; l; l = l->next) {
if (strcmp(key, l->key) == 0) {
return l->value;
}
}
return NULL;
}
char *getKindleValue(char *key) {
return getNodeValue(kindleMap, key);
}
MapList *addMapNode(MapList *map, char *key, char *value) {
MapList *ml;
ml = findNode(map, key);
if (ml) {
free(ml->value);
ml->value = value;
return map;
}
else {
ml = (MapList*)malloc(sizeof(MapList));
ml->key = key;
ml->value = value;
ml->next = map;
return ml;
}
}
void dumpMap(MapList *m) {
MapList *l;
for (l = m; l; l = l->next) {
fprintf(stderr, "%s:%s\n", l->key, l->value);
}
}
void freeMap(MapList *m) {
MapList *n;
while (m) {
n = m;
m = m->next;
free(n->key);
free(n->value);
free(n);
}
}
void parseLine(char *line) {
char *colon = strchr(line, ':');
if (colon) {
char *key, *value;
int len = colon - line;
key = (char*)malloc(len + 1);
*colon++ = 0;
strcpy(key, line);
len = strlen(colon);
value = (char*)malloc(len + 1);
strcpy(value, colon);
value[len] = 0;
kindleMap = addMapNode(kindleMap, key, value);
}
}
void dumpKindleMap() {
dumpMap(kindleMap);
}
int buildKindleMap(char *infoFile) {
int result = 0;
struct stat statbuf;
char ki[512];
DWORD len = sizeof(ki);
if (infoFile == NULL) {
HKEY regkey;
fprintf(stderr, "Attempting to locate kindle.info\n");
if (RegOpenKey(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\", &regkey) != ERROR_SUCCESS) {
fprintf(stderr, "Unable to locate kindle.info, please specify path on command line\n");
return result;
}
// if (RegGetValue(regkey, "Local AppData", NULL, NULL, ki, &len) != ERROR_SUCCESS) {
if (RegQueryValueEx(regkey, "Local AppData", NULL, NULL, ki, &len) != ERROR_SUCCESS) {
RegCloseKey(regkey);
fprintf(stderr, "Unable to locate kindle.info, please specify path on command line\n");
return result;
}
ki[len] = 0;
strncat(ki, "\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info", sizeof(ki) - 1 - strlen(ki));
infoFile = ki;
fprintf(stderr, "Found kindle.info location\n");
}
if (stat(infoFile, &statbuf) == 0) {
FILE *fd = fopen(infoFile, "rb");
char *infoBuf = (char*)malloc(statbuf.st_size + 1);
infoBuf[statbuf.st_size] = 0;
if (fread(infoBuf, statbuf.st_size, 1, fd) == 1) {
char *end = infoBuf + statbuf.st_size;
char *b = infoBuf, *e;
while (e = strchr(b, '{')) {
*e = 0;
if ((e - b) > 2) {
parseLine(b);
}
e++;
b = e;
}
if (b < end) {
parseLine(b);
}
}
else {
fprintf(stderr, "short read on info file\n");
}
free(infoBuf);
fclose(fd);
return 1;
}
return 0;
}
static unsigned int crc_table[256];
void png_crc_table_init() {
unsigned int i;
if (crc_table[255]) return;
for (i = 0; i < 256; i++) {
unsigned int n = i;
unsigned int j;
for (j = 0; j < 8; j++) {
if (n & 1) {
n = 0xEDB88320 ^ (n >> 1);
}
else {
n >>= 1;
}
}
crc_table[i] = n;
}
}
unsigned int do_crc(unsigned char *input, unsigned int len) {
unsigned int crc = 0;
unsigned int i;
png_crc_table_init();
for (i = 0; i < len; i++) {
unsigned int v = (input[i] ^ crc) & 0xff;
crc = crc_table[v] ^ (crc >> 8);
}
return crc;
}
char *decodeString = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
void doPngDecode(unsigned char *input, unsigned int len, unsigned char *output) {
// unsigned int crc_table[256];
unsigned int crc, i, x = 0;
unsigned int *out = (unsigned int*)output;
crc = bswap_l(do_crc(input, len));
memset(output, 0, 8);
for (i = 0; i < len; i++) {
output[x++] ^= input[i];
if (x == 8) x = 0;
}
out[0] ^= crc;
out[1] ^= crc;
for (i = 0; i < 8; i++) {
unsigned char v = output[i];
output[i] = decodeString[((((v >> 5) & 3) ^ v) & 0x1F) + (v >> 7)];
}
}
static char *string_32 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M";
static char *string_64 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_";
char *mazamaEncode32(unsigned char *input, unsigned int len) {
return mazamaEncode(input, len, 32);
}
char *mazamaEncode64(unsigned char *input, unsigned int len) {
return mazamaEncode(input, len, 64);
}
char *mazamaEncode(unsigned char *input, unsigned int len, unsigned char choice) {
unsigned int i;
char *enc, *out;
if (choice == 0x20) enc = string_32;
else if (choice == 0x40) enc = string_64;
else return NULL;
out = (char*)malloc(len * 2 + 1);
out[len * 2] = 0;
for (i = 0; i < len; i++) {
unsigned char v = input[i] + 128;
unsigned char q = v / choice;
unsigned char m = v % choice;
out[i * 2] = enc[q];
out[i * 2 + 1] = enc[m];
}
return out;
}
unsigned char *mazamaDecode(char *input, int *outlen) {
unsigned char *out;
int len = strlen(input);
char *dec = NULL;
int i, choice = 0x20;
*outlen = 0;
for (i = 0; i < 8 && i < len; i++) {
if (*input == string_32[i]) {
dec = string_32;
break;
}
}
if (dec == NULL) {
for (i = 0; i < 4 && i < len; i++) {
if (*input == string_64[i]) {
dec = string_64;
choice = 0x40;
break;
}
}
}
if (dec == NULL) {
return NULL;
}
out = (unsigned char*)malloc(len / 2 + 1);
out[len / 2] = 0;
for (i = 0; i < len; i += 2) {
int q, m, v;
char *p = strchr(dec, input[i]);
if (p == NULL) break;
q = p - dec;
p = strchr(dec, input[i + 1]);
if (p == NULL) break;
m = p - dec;
v = (choice * q + m) - 128;
out[(*outlen)++] = (unsigned char)v;
}
return out;
}
#ifndef HEADER_MD5_H
void md5(unsigned char *in, int len, unsigned char *md) {
MD5_CTX s;
MD5_Init(&s);
MD5_Update(&s, in, len);
MD5_Final(md, &s);
}
#endif
#ifndef HEADER_SHA_H
void sha1(unsigned char *in, int len, unsigned char *md) {
SHA_CTX s;
SHA1_Init(&s);
SHA1_Update(&s, in, len);
SHA1_Final(md, &s);
}
#endif
char *getBookPid(unsigned char *keys, unsigned int klen, unsigned char *keysValue, unsigned int kvlen) {
unsigned char *vsn, *username, *mrn_key, *kat_key, *pid;
char drive[256];
char name[256];
DWORD nlen = sizeof(name);
char *d;
char volumeName[256];
DWORD volumeSerialNumber;
char fileSystemNameBuffer[256];
char volumeID[32];
unsigned char md5sum[MD5_DIGEST_LENGTH];
unsigned char sha1sum[SHA_DIGEST_LENGTH];
SHA_CTX sha1_ctx;
char *mv;
if (GetUserName(name, &nlen) == 0) {
fprintf(stderr, "GetUserName failed\n");
return NULL;
}
fprintf(stderr, "Using UserName = \"%s\"\n", name);
d = getenv("SystemDrive");
if (d) {
strcpy(drive, d);
strcat(drive, "\\");
}
else {
strcpy(drive, "c:\\");
}
fprintf(stderr, "Using SystemDrive = \"%s\"\n", drive);
if (GetVolumeInformation(drive, volumeName, sizeof(volumeName), &volumeSerialNumber,
NULL, NULL, fileSystemNameBuffer, sizeof(fileSystemNameBuffer))) {
sprintf(volumeID, "%u", volumeSerialNumber);
}
else {
strcpy(volumeID, "9999999999");
}
fprintf(stderr, "Using VolumeSerialNumber = \"%s\"\n", volumeID);
MD5(volumeID, strlen(volumeID), md5sum);
vsn = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x20);
MD5(name, strlen(name), md5sum);
username = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x20);
MD5("MazamaRandomNumber", 18, md5sum);
mrn_key = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x40);
MD5("kindle.account.tokens", 21, md5sum);
kat_key = mazamaEncode(md5sum, MD5_DIGEST_LENGTH, 0x40);
SHA1_Init(&sha1_ctx);
mv = getKindleValue(mrn_key);
if (mv) {
DATA_BLOB DataIn;
DATA_BLOB DataOut;
DataIn.pbData = mazamaDecode(mv, (int*)&DataIn.cbData);
if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) {
char *devId = (char*)malloc(DataOut.cbData + 4 * MD5_DIGEST_LENGTH + 1);
char *finalDevId;
unsigned char pidbuf[10];
// fprintf(stderr, "CryptUnprotectData success\n");
// fwrite(DataOut.pbData, DataOut.cbData, 1, stderr);
// fprintf(stderr, "\n");
memcpy(devId, DataOut.pbData, DataOut.cbData);
strcpy(devId + DataOut.cbData, vsn);
strcat(devId + DataOut.cbData, username);
// fprintf(stderr, "Computing sha1 over %d bytes\n", DataOut.cbData + 4 * MD5_DIGEST_LENGTH);
sha1(devId, DataOut.cbData + 4 * MD5_DIGEST_LENGTH, sha1sum);
finalDevId = mazamaEncode(sha1sum, SHA_DIGEST_LENGTH, 0x20);
// fprintf(stderr, "finalDevId: %s\n", finalDevId);
SHA1_Update(&sha1_ctx, finalDevId, strlen(finalDevId));
pidbuf[8] = 0;
doPngDecode(finalDevId, 4, (unsigned char*)pidbuf);
fprintf(stderr, "Device PID: %s\n", pidbuf);
LocalFree(DataOut.pbData);
free(devId);
free(finalDevId);
}
else {
fprintf(stderr, "CryptUnprotectData failed, quitting\n");
free(kat_key);
free(mrn_key);
return NULL;
}
free(DataIn.pbData);
}
else {
fprintf(stderr, "Failed to find map node: %s\n", mrn_key);
}
mv = getKindleValue(kat_key);
if (mv) {
DATA_BLOB DataIn;
DATA_BLOB DataOut;
DataIn.pbData = mazamaDecode(mv, (int*)&DataIn.cbData);
if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) {
// fprintf(stderr, "CryptUnprotectData success\n");
// fwrite(DataOut.pbData, DataOut.cbData, 1, stderr);
// fprintf(stderr, "\n");
SHA1_Update(&sha1_ctx, DataOut.pbData, DataOut.cbData);
LocalFree(DataOut.pbData);
}
else {
fprintf(stderr, "CryptUnprotectData failed, quitting\n");
free(kat_key);
free(mrn_key);
return NULL;
}
free(DataIn.pbData);
}
else {
fprintf(stderr, "Failed to find map node: %s\n", kat_key);
}
SHA1_Update(&sha1_ctx, keys, klen);
SHA1_Update(&sha1_ctx, keysValue, kvlen);
SHA1_Final(sha1sum, &sha1_ctx);
pid = (char*)malloc(SHA_DIGEST_LENGTH * 2);
base64(sha1sum, SHA_DIGEST_LENGTH, pid);
pid[8] = 0;
free(mrn_key);
free(kat_key);
free(vsn);
free(username);
return pid;
}
static char *letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
int verifyPidChecksum(char *pid) {
int l = strlen(letters);
unsigned int crc = ~do_crc(pid, 8);
unsigned char b;
crc = crc ^ (crc >> 16);
b = crc & 0xff;
if (pid[8] != letters[((b / l) ^ (b % l)) % l]) return 0;
crc >>= 8;
b = crc & 0xff;
if (pid[9] != letters[((b / l) ^ (b % l)) % l]) return 0;
return 1;
}

View file

@ -1,100 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef __SKINUTILS_H
#define __SKINUTILS_H
typedef struct _PidList {
unsigned int numPids;
char *pidList[1]; //extra pids to try from command line
} PidList;
typedef struct _MapList {
char *key;
char *value;
struct _MapList *next;
} MapList;
extern MapList *kindleMap;
unsigned int base64(unsigned char *inbuf, unsigned int len, unsigned char *outbuf);
unsigned short bswap_s(unsigned short s);
unsigned int bswap_l(unsigned int s);
char *translateKindleKey(char *key);
MapList *findNode(MapList *map, char *key);
MapList *findKindleNode(char *key);
//don't free the result of getNodeValue;
char *getNodeValue(MapList *map, char *key);
char *getKindleValue(char *key);
MapList *addMapNode(MapList *map, char *key, char *value);
void dumpMap(MapList *m);
void freeMap(MapList *m);
int buildKindleMap(char *infoFile);
void dumpKindleMap();
//void png_crc_table_init(unsigned int *crc_table);
unsigned int do_crc(unsigned char *input, unsigned int len);
void doPngDecode(unsigned char *input, unsigned int len, unsigned char *output);
char *mazamaEncode(unsigned char *input, unsigned int len, unsigned char choice);
char *mazamaEncode32(unsigned char *input, unsigned int len);
char *mazamaEncode64(unsigned char *input, unsigned int len);
unsigned char *mazamaDecode(char *input, int *outlen);
int verifyPidChecksum(char *pid);
//If you prefer to use openssl uncomment the following
//#include <openssl/sha.h>
//#include <openssl/md5.h>
#ifndef HEADER_MD5_H
#include "md5.h"
#define MD5_DIGEST_LENGTH 16
#define MD5_CTX md5_state_t
#define MD5_Init md5_init
#define MD5_Update md5_append
#define MD5_Final(x, y) md5_finish(y, x)
#define MD5 md5
void md5(unsigned char *in, int len, unsigned char *md);
#endif
#ifndef HEADER_SHA_H
#include "sha1.h"
#define SHA_DIGEST_LENGTH 20
#define SHA_CTX sha1_state_s
#define SHA1_Init sha1_init
#define SHA1_Update sha1_update
#define SHA1_Final(x, y) sha1_finish(y, x)
#define SHA1 sha1
void sha1(unsigned char *in, int len, unsigned char *md);
#endif
char *getBookPid(unsigned char *keys, unsigned int klen, unsigned char *keysValue, unsigned int kvlen);
#endif

View file

@ -1,504 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "skinutils.h"
#include "cbuf.h"
#include "tpz.h"
#include "zlib.h"
//
// Context initialisation for the Topaz Crypto
//
void topazCryptoInit(TpzCtx *ctx, unsigned char *key, int klen) {
int i = 0;
ctx->v[0] = 0x0CAFFE19E;
for (i = 0; i < klen; i++) {
ctx->v[1] = ctx->v[0];
ctx->v[0] = ((ctx->v[0] >> 2) * (ctx->v[0] >> 7)) ^
(key[i] * key[i] * 0x0F902007);
}
}
//
// decrypt data with the context prepared by topazCryptoInit()
//
void topazCryptoDecrypt(TpzCtx *ctx, unsigned char *in, unsigned char *out, int len) {
unsigned int ctx1 = ctx->v[0];
unsigned int ctx2 = ctx->v[1];
int i;
for (i = 0; i < len; i++) {
unsigned char m = in[i] ^ (ctx1 >> 3) ^ (ctx2 << 3);
ctx2 = ctx1;
ctx1 = ((ctx1 >> 2) * (ctx1 >> 7)) ^ (m * m * 0x0F902007);
out[i] = m;
}
}
int bookReadEncodedNumber(FILE *f) {
int flag = 0;
int data = fgetc(f);
if (data == 0xFF) { //negative number flag
flag = 1;
data = fgetc(f);
}
if (data >= 0x80) {
int datax = data & 0x7F;
while (data >= 0x80) {
data = fgetc(f);
datax = (datax << 7) + (data & 0x7F);
}
data = datax;
}
if (flag) {
data = -data;
}
return data;
}
//
// Encode a number in 7 bit format
//
int encodeNumber(int number, unsigned char *out) {
unsigned char *b = out;
unsigned char flag = 0;
int len;
int neg = number < 0;
if (neg) {
number = -number + 1;
}
do {
*b++ = (number & 0x7F) | flag;
number >>= 7;
flag = 0x80;
} while (number);
if (neg) {
*b++ = 0xFF;
}
len = b - out;
b--;
while (out < b) {
unsigned char v = *out;
*out++ = *b;
*b-- = v;
}
return len;
}
//
// Get a length prefixed string from the file
//
char *bookReadString(FILE *f) {
int len = bookReadEncodedNumber(f);
char *s = (char*)malloc(len + 1);
s[len] = 0;
if (fread(s, 1, len, f) != len) {
fprintf(stderr, "String read failed at filepos %x\n", ftell(f));
free(s);
s = NULL;
}
return s;
}
//
// Read and return the data of one header record at the current book file position [[offset,compressedLength,decompressedLength],...]
//
Record *bookReadHeaderRecordData(FILE *f) {
int nbValues = bookReadEncodedNumber(f);
Record *result = NULL;
Record *tail = NULL;
unsigned int i;
if (nbValues == -1) {
fprintf(stderr, "Parse Error : EOF encountered\n");
return NULL;
}
for (i = 0; i < nbValues; i++) {
Record *r = (Record*)malloc(sizeof(Record));
r->offset = bookReadEncodedNumber(f);
r->length = bookReadEncodedNumber(f);
r->compressed = bookReadEncodedNumber(f);
r->next = NULL;
if (result == NULL) {
result = r;
}
else {
tail->next = r;
}
tail = r;
}
return result;
}
//
// Read and parse one header record at the current book file position and return the associated data [[offset,compressedLength,decompressedLength],...]
//
void freeRecordList(Record *r) {
Record *n;
while (r) {
n = r;
r = r->next;
free(n);
}
}
void freeHeaderList(HeaderRecord *r) {
HeaderRecord *n;
while (r) {
free(r->tag);
freeRecordList(r->rec);
n = r;
r = r->next;
free(n);
}
}
void freeTopazFile(TopazFile *t) {
freeHeaderList(t->hdrs);
freeMap(t->metadata);
free(t);
}
HeaderRecord *parseTopazHeaderRecord(FILE *f) {
char *tag;
Record *record;
if (fgetc(f) != 0x63) {
fprintf(stderr, "Parse Error : Invalid Header at 0x%x\n", ftell(f) - 1);
return NULL;
}
tag = bookReadString(f);
record = bookReadHeaderRecordData(f);
if (tag && record) {
HeaderRecord *r = (HeaderRecord*)malloc(sizeof(Record));
r->tag = tag;
r->rec = record;
r->next = NULL;
return r;
}
return NULL;
}
//
// Parse the header of a Topaz file, get all the header records and the offset for the payload
//
HeaderRecord *addRecord(HeaderRecord *head, HeaderRecord *r) {
HeaderRecord *i;
for (i = head; i; i = i->next) {
if (i->next == NULL) {
i->next = r;
return head;
}
}
return r;
}
TopazFile *parseTopazHeader(FILE *f) {
unsigned int numRecs, i, magic;
TopazFile *tpz;
if (fread(&magic, sizeof(magic), 1, f) != 1) {
fprintf(stderr, "Failed to read file magic\n");
return NULL;
}
if (magic != 0x305a5054) {
fprintf(stderr, "Parse Error : Invalid Header, not a Topaz file");
return NULL;
}
numRecs = fgetc(f);
tpz = (TopazFile*)calloc(sizeof(TopazFile), 1);
tpz->f = f;
for (i = 0; i < numRecs; i++) {
HeaderRecord *result = parseTopazHeaderRecord(f);
if (result == NULL) {
break;
}
tpz->hdrs = addRecord(tpz->hdrs, result);
}
if (fgetc(f) != 0x64) {
fprintf(stderr, "Parse Error : Invalid Header end at pos 0x%x\n", ftell(f) - 1);
//empty list
freeTopazFile(tpz);
return NULL;
}
tpz->bodyOffset = ftell(f);
return tpz;
}
HeaderRecord *findHeader(TopazFile *tpz, char *tag) {
HeaderRecord *hr;
for (hr = tpz->hdrs; hr; hr = hr->next) {
if (strcmp(hr->tag, tag) == 0) {
break;
}
}
return hr;
}
void freePayload(Payload *p) {
free(p->blob);
free(p);
}
//
//Get a record in the book payload, given its name and index. If necessary the record is decrypted. The record is not decompressed
//
Payload *getBookPayloadRecord(TopazFile *t, char *name, int index, int explode) {
int encrypted = 0;
int recordOffset, i, recordIndex;
Record *r;
int fileSize;
char *tag;
Payload *p;
off_t fileOffset;
HeaderRecord *hr = findHeader(t, name);
if (hr == NULL) {
fprintf(stderr, "Parse Error : Invalid Record, record %s not found\n", name);
return NULL;
}
r = hr->rec;
for (i = 0; r && i < index; i++) {
r = r->next;
}
if (r == NULL) {
fprintf(stderr, "Parse Error : Invalid Record, record %s:%d not found\n", name, index);
return NULL;
}
recordOffset = r->offset;
if (fseek(t->f, t->bodyOffset + recordOffset, SEEK_SET) == -1) {
fprintf(stderr, "Parse Error : Invalid Record offset, record %s:%d\n", name, index);
return NULL;
}
tag = bookReadString(t->f);
if (strcmp(tag, name)) {
fprintf(stderr, "Parse Error : Invalid Record offset, record %s:%d name doesn't match\n", name, index);
return NULL;
}
recordIndex = bookReadEncodedNumber(t->f);
if (recordIndex < 0) {
encrypted = 1;
recordIndex = -recordIndex - 1;
}
if (recordIndex != index) {
fprintf(stderr, "Parse Error : Invalid Record offset, record %s:%d index doesn't match\n", name, index);
return NULL;
}
fileSize = r->compressed ? r->compressed : r->length;
p = (Payload*)malloc(sizeof(Payload));
p->blob = (unsigned char*)malloc(fileSize);
p->len = fileSize;
p->name = name;
p->index = index;
fileOffset = ftell(t->f);
if (fread(p->blob, fileSize, 1, t->f) != 1) {
freePayload(p);
fprintf(stderr, "Parse Error : Failed payload read of record %s:%d offset 0x%x:0x%x\n", name, index, fileOffset, fileSize);
return NULL;
}
if (encrypted) {
TpzCtx ctx;
topazCryptoInit(&ctx, t->bookKey, 8);
topazCryptoDecrypt(&ctx, p->blob, p->blob, p->len);
}
if (r->compressed && explode) {
unsigned char *db = (unsigned char *)malloc(r->length);
uLongf dl = r->length;
switch (uncompress(db, &dl, p->blob, p->len)) {
case Z_OK:
free(p->blob);
p->blob = db;
p->len = dl;
break;
case Z_MEM_ERROR:
free(db);
fprintf(stderr, "out of memory\n");
break;
case Z_BUF_ERROR:
free(db);
fprintf(stderr, "output buffer wasn't large enough!\n");
break;
}
}
return p;
}
//
// Parse the metadata record from the book payload and return a list of [key,values]
//
char *getMetadata(TopazFile *t, char *key) {
return getNodeValue(t->metadata, key);
}
void parseMetadata(TopazFile *t) {
char *tag;
int flags, nbRecords, i;
HeaderRecord *hr = findHeader(t, "metadata");
fseek(t->f, t->bodyOffset + hr->rec->offset, SEEK_SET);
tag = bookReadString(t->f);
if (strcmp(tag, "metadata")) {
//raise CMBDTCFatal("Parse Error : Record Names Don't Match")
return;
}
flags = fgetc(t->f);
nbRecords = bookReadEncodedNumber(t->f);
for (i = 0; i < nbRecords; i++) {
char *key = bookReadString(t->f);
char *value = bookReadString(t->f);
t->metadata = addMapNode(t->metadata, key, value);
}
}
//
// Decrypt a payload record with the PID
//
void decryptRecord(unsigned char *in, int len, unsigned char *out, unsigned char *PID) {
TpzCtx ctx;
topazCryptoInit(&ctx, PID, 8); //is this length correct
topazCryptoDecrypt(&ctx, in, out, len);
}
//
// Try to decrypt a dkey record (contains the book PID)
//
unsigned char *decryptDkeyRecord(unsigned char *data, int len, unsigned char *PID) {
decryptRecord(data, len, data, PID);
//fields = unpack("3sB8sB8s3s",record);
if (strncmp(data, "PID", 3) || strncmp(data + 21, "pid", 3)) {
fprintf(stderr, "Didn't find PID magic numbers in record\n");
return NULL;
}
else if (data[3] != 8 || data[12] != 8) {
fprintf(stderr, "Record didn't contain correct length fields\n");
return NULL;
}
else if (strncmp(data + 4, PID, 8)) {
fprintf(stderr, "Record didn't contain PID\n");
return NULL;
}
return data + 13;
}
//
// Decrypt all the book's dkey records (contain the book PID)
//
unsigned char *decryptDkeyRecords(Payload *data, unsigned char *PID) {
int nbKeyRecords = data->blob[0]; //is this encoded number?
int i, idx;
idx = 1;
unsigned char *key = NULL;
// records = []
for (i = 0; i < nbKeyRecords && idx < data->len; i++) {
int length = data->blob[idx++];
key = decryptDkeyRecord(data->blob + idx, length, PID);
if (key) break; //???
idx += length;
}
return key;
}
void bufEncodeInt(cbuf *b, int i) {
unsigned char encoded[16];
int len = encodeNumber(i, encoded);
b_add_buf(b, encoded, len);
}
void bufEncodeString(cbuf *b, char *s) {
bufEncodeInt(b, strlen(s));
b_add_str(b, s);
}
void writeTopazOutputFile(TopazFile *t, FILE *out, cbuf *tpzHeaders,
cbuf *tpzBody, int explode) {
int i, numHdrs = 0;
HeaderRecord *h;
b_add_str(tpzHeaders, "TPZ0");
for (h = t->hdrs; h; h = h->next) {
if (strcmp(h->tag, "dkey")) {
numHdrs++;
}
}
bufEncodeInt(tpzHeaders, numHdrs);
b_add_byte(tpzBody, 0x40);
for (h = t->hdrs; h; h = h->next) {
Record *r;
int nr = 0, idx = 0;
if (strcmp(h->tag, "dkey") == 0) continue;
b_add_byte(tpzHeaders, 0x63);
bufEncodeString(tpzHeaders, h->tag);
for (r = h->rec; r; r = r->next) nr++;
bufEncodeInt(tpzHeaders, nr);
for (r = h->rec; r; r = r->next) {
Payload *p;
int b, e;
bufEncodeInt(tpzHeaders, tpzBody->idx);
bufEncodeString(tpzBody, h->tag);
bufEncodeInt(tpzBody, idx);
b = tpzBody->idx;
p = getBookPayloadRecord(t, h->tag, idx++, explode);
b_add_buf(tpzBody, p->blob, p->len);
e = tpzBody->idx;
bufEncodeInt(tpzHeaders, r->length); //this is length of blob portion after decompression
if (explode) {
bufEncodeInt(tpzHeaders, 0); //this is the length in the file if compressed
}
else {
bufEncodeInt(tpzHeaders, r->compressed); //this is the length in the file if compressed
}
freePayload(p);
}
}
b_add_byte(tpzHeaders, 0x64);
}

View file

@ -1,82 +0,0 @@
/*
Copyright 2010 BartSimpson aka skindle
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef __TPZ_H
#define __TPZ_H
#include <stdio.h>
#include "skinutils.h"
typedef struct _TpzCtx {
unsigned int v[2];
} TpzCtx;
void topazCryptoInit(TpzCtx *ctx, unsigned char *key, int klen);
void topazCryptoDecrypt(TpzCtx *ctx, unsigned char *in, unsigned char *out, int len);
int bookReadEncodedNumber(FILE *f);
int encodeNumber(int number, unsigned char *out);
char *bookReadString(FILE *f);
typedef struct _Payload {
unsigned char *blob;
unsigned int len;
char *name;
int index;
} Payload;
typedef struct _Record {
int offset;
int length;
int compressed;
struct _Record *next;
} Record;
typedef struct _HeaderRecord {
char *tag;
Record *rec;
struct _HeaderRecord *next;
} HeaderRecord;
typedef struct _TopazFile {
FILE *f;
HeaderRecord *hdrs;
unsigned char *bookKey;
unsigned int bodyOffset;
MapList *metadata;
PidList *pids; //extra pids to try from command line
} TopazFile;
Record *bookReadHeaderRecordData(FILE *f);
void freeRecordList(Record *r);
void freeHeaderList(HeaderRecord *r);
void freeTopazFile(TopazFile *t);
HeaderRecord *parseTopazHeaderRecord(FILE *f);
HeaderRecord *addRecord(HeaderRecord *head, HeaderRecord *r);
TopazFile *parseTopazHeader(FILE *f);
void freeTopazFile(TopazFile *tpz);
HeaderRecord *findHeader(TopazFile *tpz, char *tag);
void freePayload(Payload *p);
Payload *getBookPayloadRecord(TopazFile *t, char *name, int index, int explode);
char *getMetadata(TopazFile *t, char *key);
void parseMetadata(TopazFile *t);
void decryptRecord(unsigned char *in, int len, unsigned char *out, unsigned char *PID);
unsigned char *decryptDkeyRecord(unsigned char *data, int len, unsigned char *PID);
unsigned char *decryptDkeyRecords(Payload *data, unsigned char *PID);
void writeTopazOutputFile(TopazFile *t, FILE *out, cbuf *tpzHeaders,
cbuf *tpzBody, int explode);
#endif

View file

@ -1,332 +0,0 @@
/* zconf.h -- configuration of the zlib compression library
* Copyright (C) 1995-2005 Jean-loup Gailly.
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
#ifndef ZCONF_H
#define ZCONF_H
/*
* If you *really* need a unique prefix for all types and library functions,
* compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
*/
#ifdef Z_PREFIX
# define deflateInit_ z_deflateInit_
# define deflate z_deflate
# define deflateEnd z_deflateEnd
# define inflateInit_ z_inflateInit_
# define inflate z_inflate
# define inflateEnd z_inflateEnd
# define deflateInit2_ z_deflateInit2_
# define deflateSetDictionary z_deflateSetDictionary
# define deflateCopy z_deflateCopy
# define deflateReset z_deflateReset
# define deflateParams z_deflateParams
# define deflateBound z_deflateBound
# define deflatePrime z_deflatePrime
# define inflateInit2_ z_inflateInit2_
# define inflateSetDictionary z_inflateSetDictionary
# define inflateSync z_inflateSync
# define inflateSyncPoint z_inflateSyncPoint
# define inflateCopy z_inflateCopy
# define inflateReset z_inflateReset
# define inflateBack z_inflateBack
# define inflateBackEnd z_inflateBackEnd
# define compress z_compress
# define compress2 z_compress2
# define compressBound z_compressBound
# define uncompress z_uncompress
# define adler32 z_adler32
# define crc32 z_crc32
# define get_crc_table z_get_crc_table
# define zError z_zError
# define alloc_func z_alloc_func
# define free_func z_free_func
# define in_func z_in_func
# define out_func z_out_func
# define Byte z_Byte
# define uInt z_uInt
# define uLong z_uLong
# define Bytef z_Bytef
# define charf z_charf
# define intf z_intf
# define uIntf z_uIntf
# define uLongf z_uLongf
# define voidpf z_voidpf
# define voidp z_voidp
#endif
#if defined(__MSDOS__) && !defined(MSDOS)
# define MSDOS
#endif
#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
# define OS2
#endif
#if defined(_WINDOWS) && !defined(WINDOWS)
# define WINDOWS
#endif
#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
# ifndef WIN32
# define WIN32
# endif
#endif
#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
# ifndef SYS16BIT
# define SYS16BIT
# endif
# endif
#endif
/*
* Compile with -DMAXSEG_64K if the alloc function cannot allocate more
* than 64k bytes at a time (needed on systems with 16-bit int).
*/
#ifdef SYS16BIT
# define MAXSEG_64K
#endif
#ifdef MSDOS
# define UNALIGNED_OK
#endif
#ifdef __STDC_VERSION__
# ifndef STDC
# define STDC
# endif
# if __STDC_VERSION__ >= 199901L
# ifndef STDC99
# define STDC99
# endif
# endif
#endif
#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
# define STDC
#endif
#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
# define STDC
#endif
#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
# define STDC
#endif
#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
# define STDC
#endif
#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
# define STDC
#endif
#ifndef STDC
# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
# define const /* note: need a more gentle solution here */
# endif
#endif
/* Some Mac compilers merge all .h files incorrectly: */
#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__)
# define NO_DUMMY_DECL
#endif
/* Maximum value for memLevel in deflateInit2 */
#ifndef MAX_MEM_LEVEL
# ifdef MAXSEG_64K
# define MAX_MEM_LEVEL 8
# else
# define MAX_MEM_LEVEL 9
# endif
#endif
/* Maximum value for windowBits in deflateInit2 and inflateInit2.
* WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
* created by gzip. (Files created by minigzip can still be extracted by
* gzip.)
*/
#ifndef MAX_WBITS
# define MAX_WBITS 15 /* 32K LZ77 window */
#endif
/* The memory requirements for deflate are (in bytes):
(1 << (windowBits+2)) + (1 << (memLevel+9))
that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
plus a few kilobytes for small objects. For example, if you want to reduce
the default memory requirements from 256K to 128K, compile with
make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
Of course this will generally degrade compression (there's no free lunch).
The memory requirements for inflate are (in bytes) 1 << windowBits
that is, 32K for windowBits=15 (default value) plus a few kilobytes
for small objects.
*/
/* Type declarations */
#ifndef OF /* function prototypes */
# ifdef STDC
# define OF(args) args
# else
# define OF(args) ()
# endif
#endif
/* The following definitions for FAR are needed only for MSDOS mixed
* model programming (small or medium model with some far allocations).
* This was tested only with MSC; for other MSDOS compilers you may have
* to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
* just define FAR to be empty.
*/
#ifdef SYS16BIT
# if defined(M_I86SM) || defined(M_I86MM)
/* MSC small or medium model */
# define SMALL_MEDIUM
# ifdef _MSC_VER
# define FAR _far
# else
# define FAR far
# endif
# endif
# if (defined(__SMALL__) || defined(__MEDIUM__))
/* Turbo C small or medium model */
# define SMALL_MEDIUM
# ifdef __BORLANDC__
# define FAR _far
# else
# define FAR far
# endif
# endif
#endif
#if defined(WINDOWS) || defined(WIN32)
/* If building or using zlib as a DLL, define ZLIB_DLL.
* This is not mandatory, but it offers a little performance increase.
*/
# ifdef ZLIB_DLL
# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
# ifdef ZLIB_INTERNAL
# define ZEXTERN extern __declspec(dllexport)
# else
# define ZEXTERN extern __declspec(dllimport)
# endif
# endif
# endif /* ZLIB_DLL */
/* If building or using zlib with the WINAPI/WINAPIV calling convention,
* define ZLIB_WINAPI.
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
*/
# ifdef ZLIB_WINAPI
# ifdef FAR
# undef FAR
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
# define ZEXPORT WINAPI
# ifdef WIN32
# define ZEXPORTVA WINAPIV
# else
# define ZEXPORTVA FAR CDECL
# endif
# endif
#endif
#if defined (__BEOS__)
# ifdef ZLIB_DLL
# ifdef ZLIB_INTERNAL
# define ZEXPORT __declspec(dllexport)
# define ZEXPORTVA __declspec(dllexport)
# else
# define ZEXPORT __declspec(dllimport)
# define ZEXPORTVA __declspec(dllimport)
# endif
# endif
#endif
#ifndef ZEXTERN
# define ZEXTERN extern
#endif
#ifndef ZEXPORT
# define ZEXPORT
#endif
#ifndef ZEXPORTVA
# define ZEXPORTVA
#endif
#ifndef FAR
# define FAR
#endif
#if !defined(__MACTYPES__)
typedef unsigned char Byte; /* 8 bits */
#endif
typedef unsigned int uInt; /* 16 bits or more */
typedef unsigned long uLong; /* 32 bits or more */
#ifdef SMALL_MEDIUM
/* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
# define Bytef Byte FAR
#else
typedef Byte FAR Bytef;
#endif
typedef char FAR charf;
typedef int FAR intf;
typedef uInt FAR uIntf;
typedef uLong FAR uLongf;
#ifdef STDC
typedef void const *voidpc;
typedef void FAR *voidpf;
typedef void *voidp;
#else
typedef Byte const *voidpc;
typedef Byte FAR *voidpf;
typedef Byte *voidp;
#endif
#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */
# include <sys/types.h> /* for off_t */
# include <unistd.h> /* for SEEK_* and off_t */
# ifdef VMS
# include <unixio.h> /* for off_t */
# endif
# define z_off_t off_t
#endif
#ifndef SEEK_SET
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
#endif
#ifndef z_off_t
# define z_off_t long
#endif
#if defined(__OS400__)
# define NO_vsnprintf
#endif
#if defined(__MVS__)
# define NO_vsnprintf
# ifdef FAR
# undef FAR
# endif
#endif
/* MVS linker does not support external names larger than 8 bytes */
#if defined(__MVS__)
# pragma map(deflateInit_,"DEIN")
# pragma map(deflateInit2_,"DEIN2")
# pragma map(deflateEnd,"DEEND")
# pragma map(deflateBound,"DEBND")
# pragma map(inflateInit_,"ININ")
# pragma map(inflateInit2_,"ININ2")
# pragma map(inflateEnd,"INEND")
# pragma map(inflateSync,"INSY")
# pragma map(inflateSetDictionary,"INSEDI")
# pragma map(compressBound,"CMBND")
# pragma map(inflate_table,"INTABL")
# pragma map(inflate_fast,"INFA")
# pragma map(inflate_copyright,"INCOPY")
#endif
#endif /* ZCONF_H */

File diff suppressed because it is too large Load diff

View file

@ -1,23 +0,0 @@
Readme.txt
From Apprentice Alf's Blog:
When Amazon released Kindle for PC, there was a lot of interest in finding the PID used by an installed copy. Unfortunately, it turned out that Amazon had taken the opportunity to increase the complexity of the DRM applied to ebooks downloaded for use with Kindle for PC. In short, every book has its own PID.
The current best method is a script called K4PCDeDRM.py, which takes code from several previous tools and puts them together, giving a one-script solution to removing the DRM that is not dependent on the exact version of the Kindle for PC software.
To use this script requires Python Version 2.X 32 bit version. On Windows, we recommend downloading ActiveState's ActivePython for Windows (Version 2.X NOT 3.X (32bit).
We strongly recommend that at the moment people with Kindle for Windows should use K4PCDeDRM.pyw instead of any of the other methods (skindle, unswindle).
K4PCDeDRM is available in the large archive mentioned in the first post.
To run it simply completely extract the tools archive and open the Kindle_4_PC_Tools. Then double-click on K4PCDeDRM.pyw to run the gui version.
Please note, the tools archive must be on the same machine in the same account as the Kindle for PC application for things to work.
FYI:
Books downloaded to Kindle for PC are stored in a folder called “My Kindle Content” in the current users home folder.
IMPORTANT: There are two kinds of Kindle ebooks. Mobipocket and Topaz. For Topaz books its really necessary to use the Topaz specific tools mentioned in this post which not only remove the DRM but also convert the Topaz file into a format that can be converted into other formats.

View file

@ -1,682 +0,0 @@
#!/usr/bin/env python
#
# This is a WINDOWS python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# It can run standalone to convert K4PC files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
# K4PC files with DRM is no londer a multi-step process.
#
# ***NOTE*** Calibre and K4PC must be installed on the same windows machine
# for the plugin version to function properly.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file and import that ZIP into Calibre
# using its plugin configuration GUI.
#
# Thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump from
# which this script steals most unashamedly.
#
# Changelog
# 0.01 - Initial version - Utilizes skindle and CMBDTC method of obtaining
# book specific pids from K4PC books. If Calibre and K4PC are installed
# on the same windows machine, Calibre plugin functionality is once
# again restored.
"""
Comprehensive Mazama Book DRM with Topaz Cryptography V2.0
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdBHJ4CNc6DNFCw4MRCw4SWAK6
M8hYfnNEI0yQmn5Ti+W8biT7EatpauE/5jgQMPBmdNrDr1hbHyHBSP7xeC2qlRWC
B62UCxeu/fpfnvNHDN/wPWWH4jynZ2M6cdcnE5LQ+FfeKqZn7gnG2No1U9h7oOHx
y2/pHuYme7U1TsgSjwIDAQAB
-----END PUBLIC KEY-----
"""
from __future__ import with_statement
import csv
import sys
import os
import getopt
import zlib
import binascii
from struct import pack
from struct import unpack
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
string_at, Structure, c_void_p, cast
import _winreg as winreg
import traceback
import hashlib
__version__ = '0.01'
global kindleDatabase
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class DrmException(Exception):
pass
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
def GetSystemDirectory():
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
GetSystemDirectoryW.restype = c_uint
def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1)
GetSystemDirectoryW(buffer, len(buffer))
return buffer.value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory()
def GetVolumeSerialNumber():
GetVolumeInformationW = kernel32.GetVolumeInformationW
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
POINTER(c_uint), POINTER(c_uint),
POINTER(c_uint), c_wchar_p, c_uint]
GetVolumeInformationW.restype = c_uint
def GetVolumeSerialNumber(path):
vsn = c_uint(0)
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
return vsn.value
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetUserName():
GetUserNameW = advapi32.GetUserNameW
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
GetUserNameW.restype = c_uint
def GetUserName():
buffer = create_unicode_buffer(32)
size = c_uint(len(buffer))
while not GetUserNameW(buffer, byref(size)):
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
return buffer.value.encode('utf-16-le')[::2]
return GetUserName
GetUserName = GetUserName()
def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)):
raise DrmException("Failed to Unprotect Data")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
#
# Returns the MD5 digest of "message"
#
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
#
# Returns the MD5 digest of "message"
#
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
#
# Locate and open the Kindle.info file.
#
def openKindleInfo():
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
#
# Parse the Kindle.info file and return the records as a list of key-values
#
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
#
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string. (Totally not optimal)
#
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return name
#
# Print all the records from the kindle.info file.
#
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------\n")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
#
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
return CryptUnprotectData(encryptedValue,"")
#
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
#
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
#
# 8 bits to six bits encoding from hash to generate PID string
#
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
#
# Hash the bytes in data and then encode the digest with the characters in map
#
def encodeHash(data,map):
return encode(MD5(data),map)
#
# Encode the bytes in data with the characters in map
#
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
#
# Decode the string in data with the characters in map. Returns the decoded bytes
#
def decode(data,map):
result = ""
for i in range (0,len(data),2):
high = map.find(data[i])
low = map.find(data[i+1])
value = (((high * 0x40) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
#
# Encryption table used to generate the device PID
#
def generatePidEncryptionTable() :
table = []
for counter1 in range (0,0x100):
value = counter1
for counter2 in range (0,8):
if (value & 1 == 0) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table.append(value)
return table
#
# Seed value used to generate the device PID
#
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
#
# Generate the device PID
#
def generateDevicePID(table,dsn,nbRoll):
seed = generatePidSeed(table,dsn)
pidAscii = ""
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
pid[index] = pid[index] ^ ord(dsn[counter])
index = (index+1) %8
for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index]
return pidAscii
#
# Returns two bit at offset from a bit field
#
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
#
# Returns the six bits at offset from a bit field
#
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
#
# MobiDeDrm-0.16 Stuff
#
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
# 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 getSizeOfTrailingDataEntries(ptr, size, flags):
def getSizeOfTrailingDataEntry(ptr, size):
bitpos, result = 0, 0
if size <= 0:
return result
while True:
v = ord(ptr[size-1])
result |= (v & 0x7F) << bitpos
bitpos += 7
size -= 1
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
return result
num = 0
testflags = flags >> 1
while testflags:
if testflags & 1:
num += getSizeOfTrailingDataEntry(ptr, size - num)
testflags >>= 1
# Multibyte data, if present, is included in the encryption, so
# we do not need to check the low bit.
# if flags & 1:
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num
#
# This class does all the heavy lifting.
#
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 = unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = unpack('>LL16sLL', cookie)
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
found_key = finalkey
break
if not found_key:
# Then try the default encoding that doesn't require a PID
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
verification, size, type, cksum, cookie = unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = unpack('>LL16sLL', cookie)
if verification == ver and cksum == temp_key_sum:
found_key = finalkey
break
return found_key
def __init__(self, data_file):
self.data_file = data_file
header = data_file[0:72]
if header[0x3C:0x3C+8] != 'BOOKMOBI':
raise DrmException("invalid file format")
self.num_sections, = unpack('>H', data_file[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = 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, = unpack('>H', sect[0x8:0x8+2])
mobi_length, = unpack('>L',sect[0x14:0x18])
mobi_version, = unpack('>L',sect[0x68:0x6C])
extra_data_flags = 0
print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
if (mobi_length >= 0xE4) and (mobi_version >= 5):
extra_data_flags, = unpack('>H', sect[0xF2:0xF4])
print "Extra Data Flags = %d" %extra_data_flags
crypto_type, = unpack('>H', sect[0xC:0xC+2])
if crypto_type == 0:
print "This book is not encrypted."
else:
if crypto_type == 1:
raise DrmException("cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)
# determine the EXTH Offset.
exth_off = unpack('>I', sect[20:24])[0] + 16 + self.sections[0][0]
# Grab the entire EXTH block and feed it to the getK4PCPids function.
exth = data_file[exth_off:self.sections[0+1][0]]
pid = getK4PCPids(exth)
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = unpack('>LLLL', sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("no PIDs found in this file")
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 "\nDecrypting. Please wait . . .",
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
if i%100 == 0:
print ".",
# print "record %d, extra_size %d" %(i,extra_size)
new_data += PC1(found_key, data[0:len(data) - extra_size])
if extra_size > 0:
new_data += data[-extra_size:]
#self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data
print "done!"
print "\nPlease only use your new-found powers for good."
def getResult(self):
return self.data_file
#
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid.
#
def getK4PCPids(exth):
global kindleDatabase
try:
kindleDatabase = parseKindleInfo()
except Exception as message:
print(message)
if kindleDatabase != None :
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(str(GetVolumeSerialNumber(GetSystemDirectory().split('\\')[0] + '\\')),charMap1)
# Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode to calculate the DSN
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
print("\nDSN: " + DSN)
# Compute the device PID (for which I can tell, is used for nothing).
# But hey, stuff being printed out is apparently cool.
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
print("Device PID: " + devicePID)
# Compute book PID
exth_records = {}
nitems, = unpack('>I', exth[8:12])
pos = 12
# Parse the EXTH records, storing data indexed by type
for i in xrange(nitems):
type, size = unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
exth_records[type] = content
pos += size
# Grab the contents of the type 209 exth record
if exth_records[209] != None:
data = exth_records[209]
else:
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4PC file?")
# Parse the 209 data to find the the exth record with the token data.
# The last character of the 209 data points to the record with the token.
# Always 208 from my experience, but I'll leave the logic in case that changes.
for i in xrange(len(data)):
if ord(data[i]) != 0:
if exth_records[ord(data[i])] != None:
token = exth_records[ord(data[i])]
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
print("Account Token: " + kindleAccountToken)
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
bookPID = encodePID(pidHash)
if exth_records[503] != None:
print "Pid for " + exth_records[503] + ": " + bookPID
else:
print ("Book PID: " + bookPID )
return bookPID
raise DrmException("\nCould not access K4PC data - Perhaps K4PC is not installed/configured?")
return null
if not __name__ == "__main__":
from calibre.customize import FileTypePlugin
class K4PCDeDRM(FileTypePlugin):
name = 'K4PCDeDRM' # Name of the plugin
description = 'Removes DRM from K4PC files'
supported_platforms = ['windows'] # Platforms this plugin will run on
author = 'DiapDealer' # The author of this plugin
version = (0, 0, 1) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
def run(self, path_to_ebook):
from calibre.gui2 import is_ok_to_use_qt
from PyQt4.Qt import QMessageBox
data_file = file(path_to_ebook, 'rb').read()
try:
unlocked_file = DrmStripper(data_file).getResult()
except DrmException:
# ignore the error
pass
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "K4PCDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
d.show()
d.raise_()
d.exec_()
return path_to_ebook
#def customization_help(self, gui=False):
# return 'Enter PID (separate multiple PIDs with comma)'
if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout)
print ('K4PCDeDrm v%(__version__)s '
'provided DiapDealer.' % globals())
if len(sys.argv)<3:
print "Removes DRM protection from K4PC books"
print "Usage:"
print " %s <infile> <outfile>" % sys.argv[0]
sys.exit(1)
else:
infile = sys.argv[1]
outfile = sys.argv[2]
data_file = file(infile, 'rb').read()
try:
strippedFile = DrmStripper(data_file)
file(outfile, 'wb').write(strippedFile.getResult())
except DrmException, e:
print "Error: %s" % e
sys.exit(1)
sys.exit(0)

View file

@ -4,15 +4,15 @@
import sys
sys.path.append('lib')
import os, os.path, urllib
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
from scrolltextwidget import ScrolledText
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
class MainDialog(Tkinter.Frame):
def __init__(self, root):
@ -20,14 +20,14 @@ class MainDialog(Tkinter.Frame):
self.root = root
self.interval = 2000
self.p2 = None
self.status = Tkinter.Label(self, text='Remove Encryption from a Mobi eBook')
self.status = Tkinter.Label(self, text='Remove Encryption from a K4PC, K4M, or Mobi eBook')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text='Mobi eBook input file').grid(row=0, sticky=Tkconstants.E)
Tkinter.Label(body, text='K4 or Mobi eBook input file').grid(row=0, sticky=Tkconstants.E)
self.mobipath = Tkinter.Entry(body, width=50)
self.mobipath.grid(row=0, column=1, sticky=sticky)
cwd = os.getcwdu()
@ -36,21 +36,24 @@ class MainDialog(Tkinter.Frame):
button = Tkinter.Button(body, text="...", command=self.get_mobipath)
button.grid(row=0, column=2)
Tkinter.Label(body, text='Name for Unencrypted Output File').grid(row=1, sticky=Tkconstants.E)
Tkinter.Label(body, text='Directory for the Unencrypted Output File').grid(row=1, sticky=Tkconstants.E)
self.outpath = Tkinter.Entry(body, width=50)
self.outpath.grid(row=1, column=1, sticky=sticky)
self.outpath.insert(0, '')
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
outname = cwd
self.outpath.insert(0, outname)
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=1, column=2)
Tkinter.Label(body, text='10 Character PID').grid(row=2, sticky=Tkconstants.E)
self.pidnum = Tkinter.StringVar()
self.pidinfo = Tkinter.Entry(body, width=12, textvariable=self.pidnum)
Tkinter.Label(body, text='Comma Separated List of 10 Character PIDs (no spaces)').grid(row=2, sticky=Tkconstants.E)
self.pidnums = Tkinter.StringVar()
self.pidinfo = Tkinter.Entry(body, width=50, textvariable=self.pidnums)
self.pidinfo.grid(row=2, column=1, sticky=sticky)
msg1 = 'Conversion Log \n\n'
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD)
self.stext.grid(row=3, column=0, columnspan=2,sticky=sticky)
self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky)
self.stext.insert(Tkconstants.END,msg1)
buttons = Tkinter.Frame(self)
@ -73,8 +76,10 @@ class MainDialog(Tkinter.Frame):
text = self.p2.readerr()
text += self.p2.read()
msg = text + '\n\n' + 'Encryption successfully removed\n'
if poll != 0:
if poll == 1:
msg = text + '\n\n' + 'Error: Encryption Removal Failed\n'
if poll == 2:
msg = text + '\n\n' + 'Input File was Not Encrypted - No Output File Needed\n'
self.showCmdOutput(msg)
self.p2 = None
self.sbotton.configure(state='normal')
@ -95,16 +100,16 @@ class MainDialog(Tkinter.Frame):
return
# run as a subprocess via pipes and collect stdout
def mobirdr(self, infile, outfile, pidnum):
def mobirdr(self, infile, outfile, pidnums):
# os.putenv('PYTHONUNBUFFERED', '1')
cmdline = 'python ./lib/mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"'
if sys.platform[0:3] == 'win':
cmdline = 'python ./lib/k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
if sys.platform.startswith('win'):
search_path = os.environ['PATH']
search_path = search_path.lower()
if search_path.find('python') >= 0:
cmdline = 'python lib\mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"'
cmdline = 'python lib\k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
else :
cmdline = 'lib\mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"'
cmdline = 'lib\k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
@ -112,8 +117,10 @@ class MainDialog(Tkinter.Frame):
def get_mobipath(self):
cpath = self.mobipath.get()
mobipath = tkFileDialog.askopenfilename(
parent=None, title='Select Mobi eBook File',
initialdir = cpath,
parent=None, title='Select K4PC, K4M or Mobi eBook File',
defaultextension='.prc', filetypes=[('Mobi eBook File', '.prc'), ('Mobi eBook File', '.azw'),('Mobi eBook File', '.mobi'),
('All Files', '.*')])
if mobipath:
@ -123,15 +130,11 @@ class MainDialog(Tkinter.Frame):
return
def get_outpath(self):
mobipath = self.mobipath.get()
initname = os.path.basename(mobipath)
p = initname.find('.')
if p >= 0: initname = initname[0:p]
initname += '_nodrm.mobi'
outpath = tkFileDialog.asksaveasfilename(
parent=None, title='Select Unencrypted Mobi File to produce',
defaultextension='.mobi', initialfile=initname,
filetypes=[('Mobi files', '.mobi'), ('All files', '.*')])
cwd = os.getcwdu()
cwd = cwd.encode('utf-8')
outpath = tkFileDialog.askdirectory(
parent=None, title='Directory to Store Unencrypted file into',
initialdir=cwd, initialfile=None)
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
@ -151,29 +154,34 @@ class MainDialog(Tkinter.Frame):
self.sbotton.configure(state='disabled')
mobipath = self.mobipath.get()
outpath = self.outpath.get()
pidnum = self.pidinfo.get()
pidnums = self.pidinfo.get()
if not mobipath or not os.path.exists(mobipath):
self.status['text'] = 'Specified Mobi eBook file does not exist'
self.status['text'] = 'Specified K4PC, K4M or Mobi eBook file does not exist'
self.sbotton.configure(state='normal')
return
if not outpath:
self.status['text'] = 'No output file specified'
self.status['text'] = 'No output directory specified'
self.sbotton.configure(state='normal')
return
if not pidnum or pidnum == '':
self.status['text'] = 'No PID specified'
if not os.path.isdir(outpath):
self.status['text'] = 'Error specified output directory does not exist'
self.sbotton.configure(state='normal')
return
# default output file name to be input file name + '_nodrm.mobi'
initname = os.path.splitext(os.path.basename(mobipath))[0]
initname += '_nodrm.mobi'
outpath += os.sep + initname
log = 'Command = "python mobidedrm.py"\n'
log += 'Mobi Path = "'+ mobipath + '"\n'
log = 'Command = "python k4mobidedrm.py"\n'
log += 'K4PC, K4M or Mobi Path = "'+ mobipath + '"\n'
log += 'Output File = "' + outpath + '"\n'
log += 'PID = "' + pidnum + '"\n'
log += 'PID list = "' + pidnums + '"\n'
log += '\n\n'
log += 'Please Wait ...\n\n'
log = log.encode('utf-8')
self.stext.insert(Tkconstants.END,log)
self.p2 = self.mobirdr(mobipath, outpath, pidnum)
self.p2 = self.mobirdr(mobipath, outpath, pidnums)
# python does not seem to allow you to create
# your own eventloop which every other gui does - strange
@ -185,7 +193,7 @@ class MainDialog(Tkinter.Frame):
def main(argv=None):
root = Tkinter.Tk()
root.title('Mobi eBook Encryption Removal')
root.title('K4PC/K4M/Mobi eBook Encryption Removal')
root.resizable(True, False)
root.minsize(300, 0)
MainDialog(root).pack(fill=Tkconstants.X, expand=1)

View file

@ -0,0 +1,23 @@
K4MobiDeDRM
This tools combines functionality of MobiDeDRM with that of K4PCDeDRM, K4MDeDRM, and K4DeDRM. Effectively, it provides one-stop shopping for all your Mobipocket, Kindle for iPhone/iPad/iPodTouch, Kindle for PC, and Kindle for Mac needs.
****
Please Note: If you a happy user of MobiDeDRM, K4DeDRM, K4PCDeDRM, or K4MUnswindle, please continue to use these programs as there is no additional capability provided by this tool over the others. In the long run, if you have problems with any of those tools, you might want to try this one as it will continue under development eventually replacing all of those tools.
****
1. double-click on K4MobiDeDRM.pyw
2. In the window that opens:
hit the first '...' button to locate your DRM Kindle-style ebook
3. Then hit the second '...' button to select an output directory for the unlocked file
4. Then add in any PIDs you need from KindleV1, Kindle for iPhone/iPad/iPodTouch, or other single PID devices to the provided box as a comma separated list of 10 digit PID numbers.
If this is a Kindle for Mac or a Kindle for PC book then you can leave this box blank
5. hit the 'Start' button
After a short delay, you should see progress in the Conversion Log window indicating is the unlocking was a success or failure.

View file

@ -0,0 +1,490 @@
#!/usr/bin/env python
# engine to remove drm from Kindle for Mac and Kindle for PC books
# for personal use for archiving and converting your ebooks
# PLEASE DO NOT PIRATE EBOOKS!
# We want all authors and publishers, and eBook stores to live
# long and prosperous lives but at the same time we just want to
# be able to read OUR books on whatever device we want and to keep
# readable for a long, long time
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
# and many many others
# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
# K4 or Mobi with DRM is no londer a multi-step process.
#
# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook
# then calibre must be installed on the same machine and in the same account as K4PC or K4M
# for the plugin version to function properly.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines
# and import that ZIP into Calibre using its plugin configuration GUI.
from __future__ import with_statement
__version__ = '1.1'
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
import sys
import os, csv, getopt
import binascii
import zlib
from struct import pack, unpack, unpack_from
#Exception Handling
class DrmException(Exception):
pass
#
# crypto digestroutines
#
import hashlib
def MD5(message):
ctx = hashlib.md5()
ctx.update(message)
return ctx.digest()
def SHA1(message):
ctx = hashlib.sha1()
ctx.update(message)
return ctx.digest()
# determine if we are running as a calibre plugin
if 'calibre' in sys.modules:
inCalibre = True
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
else:
inCalibre = False
#
# start of Kindle specific routines
#
if not inCalibre:
import mobidedrm
if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
global kindleDatabase
# Encode the bytes in data with the characters in map
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
# Hash the bytes in data and then encode the digest with the characters in map
def encodeHash(data,map):
return encode(MD5(data),map)
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
result = ""
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
result += pack("B",value)
return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo():
DB = {}
infoReader = openKindleInfo()
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey):
global kindleDatabase
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
if sys.platform.startswith('win'):
return CryptUnprotectData(encryptedValue,"")
else:
cleartext = CryptUnprotectData(encryptedValue)
return decode(cleartext, charMap1)
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
def getKindleInfoValueForKey(key):
return getKindleInfoValueForHash(encodeHash(key,charMap2))
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
def findNameForHash(hash):
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
result = ""
for name in names:
if hash == encodeHash(name, charMap2):
result = name
break
return result
# Print all the records from the kindle.info file (option -i)
def printKindleInfo():
for record in kindleDatabase:
name = findNameForHash(record)
if name != "" :
print (name)
print ("--------------------------")
else :
print ("Unknown Record")
print getKindleInfoValueForHash(record)
print "\n"
#
# PID generation routines
#
# Returns two bit at offset from a bit field
def getTwoBitsFromBitField(bitField,offset):
byteNumber = offset // 4
bitPosition = 6 - 2*(offset % 4)
return ord(bitField[byteNumber]) >> bitPosition & 3
# Returns the six bits at offset from a bit field
def getSixBitsFromBitField(bitField,offset):
offset *= 3
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
return value
# 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash):
global charMap3
PID = ""
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
# Encryption table used to generate the device PID
def generatePidEncryptionTable() :
table = []
for counter1 in range (0,0x100):
value = counter1
for counter2 in range (0,8):
if (value & 1 == 0) :
value = value >> 1
else :
value = value >> 1
value = value ^ 0xEDB88320
table.append(value)
return table
# Seed value used to generate the device PID
def generatePidSeed(table,dsn) :
value = 0
for counter in range (0,4) :
index = (ord(dsn[counter]) ^ value) &0xFF
value = (value >> 8) ^ table[index]
return value
# Generate the device PID
def generateDevicePID(table,dsn,nbRoll):
seed = generatePidSeed(table,dsn)
pidAscii = ""
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
pid[index] = pid[index] ^ ord(dsn[counter])
index = (index+1) %8
for counter in range (0,8):
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
pidAscii += charMap4[index]
return pidAscii
# convert from 8 digit PID to 10 digit PID with checksum
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
class MobiPeek:
def loadSection(self, section):
before, after = self.sections[section:section+2]
self.f.seek(before)
return self.f.read(after - before)
def __init__(self, filename):
self.f = file(filename, 'rb')
self.header = self.f.read(78)
self.ident = self.header[0x3C:0x3C+8]
if self.ident != 'BOOKMOBI' and self.ident != 'TEXtREAd':
raise DrmException('invalid file format')
self.num_sections, = unpack_from('>H', self.header, 76)
sections = self.f.read(self.num_sections*8)
self.sections = unpack_from('>%dL' % (self.num_sections*2), sections, 0)[::2] + (0xfffffff, )
self.sect0 = self.loadSection(0)
self.f.close()
def getBookTitle(self):
# get book title
toff, tlen = unpack('>II', self.sect0[0x54:0x5c])
tend = toff + tlen
title = self.sect0[toff:tend]
return title
def getexthData(self):
# if exth region exists then grab it
# get length of this header
length, type, codepage, unique_id, version = unpack('>LLLLL', self.sect0[20:40])
exth_flag, = unpack('>L', self.sect0[0x80:0x84])
exth = ''
if exth_flag & 0x40:
exth = self.sect0[16 + length:]
return exth
def isNotEncrypted(self):
lock_type, = unpack('>H', self.sect0[0xC:0xC+2])
if lock_type == 0:
return True
return False
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid.
def getK4Pids(exth, title):
global kindleDatabase
try:
kindleDatabase = parseKindleInfo()
except Exception as message:
print(message)
if kindleDatabase != None :
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
# Get the current user name
encodedUsername = encodeHash(GetUserName(),charMap1)
# concat, hash and encode to calculate the DSN
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
print("\nDSN: " + DSN)
# Compute the device PID (for which I can tell, is used for nothing).
# But hey, stuff being printed out is apparently cool.
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
print("Device PID: " + checksumPid(devicePID))
# Compute book PID
exth_records = {}
nitems, = unpack('>I', exth[8:12])
pos = 12
# Parse the exth records, storing data indexed by type
for i in xrange(nitems):
type, size = unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
exth_records[type] = content
pos += size
# Grab the contents of the type 209 exth record
if exth_records[209] != None:
data = exth_records[209]
else:
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4 file?")
# Parse the 209 data to find the the exth record with the token data.
# The last character of the 209 data points to the record with the token.
# Always 208 from my experience, but I'll leave the logic in case that changes.
for i in xrange(len(data)):
if ord(data[i]) != 0:
if exth_records[ord(data[i])] != None:
token = exth_records[ord(data[i])]
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
print("Account Token: " + kindleAccountToken)
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
if exth_records[503] != None:
print "Pid for " + exth_records[503] + ": " + bookPID
else:
print "Pid for " + title + ":" + bookPID
return bookPID
raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
return null
#
# Main
#
def main(argv=sys.argv):
global kindleDatabase
import mobidedrm
print ('K4MobiDeDrm v%(__version__)s '
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
if len(argv)<3:
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
print "Usage:"
print " %s <infile> <outfile> [<pidnums>]" % argv[0]
return 1
if len(argv) == 4:
pidnums = argv[3]
if len(argv) == 3:
pidnums = ""
kindleDatabase = None
infile = argv[1]
outfile = argv[2]
try:
# first try with K4PC/K4M
ex = MobiPeek(infile)
if ex.isNotEncrypted():
print "File was Not Encrypted"
return 2
title = ex.getBookTitle()
exth = ex.getexthData()
pid = getK4Pids(exth, title)
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
except DrmException:
pass
except mobidedrm.DrmException:
pass
else:
file(outfile, 'wb').write(unlocked_file)
return 0
# now try from the pid list
pids = pidnums.split(',')
for pid in pids:
try:
print 'Trying: "'+ pid + '"'
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
except mobidedrm.DrmException:
pass
else:
file(outfile, 'wb').write(unlocked_file)
return 0
# we could not unencrypt book
print "Error: Could Not Unencrypt Book"
return 1
if __name__ == '__main__':
sys.stdout=Unbuffered(sys.stdout)
sys.exit(main())
if not __name__ == "__main__" and inCalibre:
from calibre.customize import FileTypePlugin
class K4DeDRM(FileTypePlugin):
name = 'K4PC, K4Mac, Mobi DeDRM' # Name of the plugin
description = 'Removes DRM from K4PC, K4Mac, and Mobi files. \
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 0, 1) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
def run(self, path_to_ebook):
from calibre.gui2 import is_ok_to_use_qt
from PyQt4.Qt import QMessageBox
global kindleDatabase
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
import mobidedrm
pidnums = self.site_customization
# first try with book specifc pid from K4PC or K4M
try:
kindleDatabase = None
ex = MobiPeek(path_to_ebook)
if ex.isNotEncrypted():
return path_to_ebook
title = ex.getBookTitle()
exth = ex.getexthData()
pid = getK4Pids(exth, title)
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
except DrmException:
pass
except mobidedrm.DrmException:
pass
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
# now try from the pid list
pids = pidnums.split(',')
for pid in pids:
try:
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook, pid)
except mobidedrm.DrmException:
pass
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
#if you reached here then no luck raise and exception
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
d.show()
d.raise_()
d.exec_()
raise Exception("K4MobiDeDRM plugin could not decode the file")
return ""
def customization_help(self, gui=False):
return 'Enter each 10 character PID separated by a comma (no spaces).'

View file

@ -0,0 +1,319 @@
# standlone set of Mac OSX specific routines needed for K4DeDRM
from __future__ import with_statement
import sys
import os
#Exception Handling
class K4MDrmException(Exception):
pass
import signal
import threading
import subprocess
from subprocess import Popen, PIPE, STDOUT
# **heavily** chopped up and modfied version of asyncproc.py
# to make it actually work on Windows as well as Mac/Linux
# For the original see:
# "http://www.lysator.liu.se/~bellman/download/"
# author is "Thomas Bellman <bellman@lysator.liu.se>"
# available under GPL version 3 or Later
# create an asynchronous subprocess whose output can be collected in
# a non-blocking manner
# What a mess! Have to use threads just to get non-blocking io
# in a cross-platform manner
# luckily all thread use is hidden within this class
class Process(object):
def __init__(self, *params, **kwparams):
if len(params) <= 3:
kwparams.setdefault('stdin', subprocess.PIPE)
if len(params) <= 4:
kwparams.setdefault('stdout', subprocess.PIPE)
if len(params) <= 5:
kwparams.setdefault('stderr', subprocess.PIPE)
self.__pending_input = []
self.__collected_outdata = []
self.__collected_errdata = []
self.__exitstatus = None
self.__lock = threading.Lock()
self.__inputsem = threading.Semaphore(0)
self.__quit = False
self.__process = subprocess.Popen(*params, **kwparams)
if self.__process.stdin:
self.__stdin_thread = threading.Thread(
name="stdin-thread",
target=self.__feeder, args=(self.__pending_input,
self.__process.stdin))
self.__stdin_thread.setDaemon(True)
self.__stdin_thread.start()
if self.__process.stdout:
self.__stdout_thread = threading.Thread(
name="stdout-thread",
target=self.__reader, args=(self.__collected_outdata,
self.__process.stdout))
self.__stdout_thread.setDaemon(True)
self.__stdout_thread.start()
if self.__process.stderr:
self.__stderr_thread = threading.Thread(
name="stderr-thread",
target=self.__reader, args=(self.__collected_errdata,
self.__process.stderr))
self.__stderr_thread.setDaemon(True)
self.__stderr_thread.start()
def pid(self):
return self.__process.pid
def kill(self, signal):
self.__process.send_signal(signal)
# check on subprocess (pass in 'nowait') to act like poll
def wait(self, flag):
if flag.lower() == 'nowait':
rc = self.__process.poll()
else:
rc = self.__process.wait()
if rc != None:
if self.__process.stdin:
self.closeinput()
if self.__process.stdout:
self.__stdout_thread.join()
if self.__process.stderr:
self.__stderr_thread.join()
return self.__process.returncode
def terminate(self):
if self.__process.stdin:
self.closeinput()
self.__process.terminate()
# thread gets data from subprocess stdout
def __reader(self, collector, source):
while True:
data = os.read(source.fileno(), 65536)
self.__lock.acquire()
collector.append(data)
self.__lock.release()
if data == "":
source.close()
break
return
# thread feeds data to subprocess stdin
def __feeder(self, pending, drain):
while True:
self.__inputsem.acquire()
self.__lock.acquire()
if not pending and self.__quit:
drain.close()
self.__lock.release()
break
data = pending.pop(0)
self.__lock.release()
drain.write(data)
# non-blocking read of data from subprocess stdout
def read(self):
self.__lock.acquire()
outdata = "".join(self.__collected_outdata)
del self.__collected_outdata[:]
self.__lock.release()
return outdata
# non-blocking read of data from subprocess stderr
def readerr(self):
self.__lock.acquire()
errdata = "".join(self.__collected_errdata)
del self.__collected_errdata[:]
self.__lock.release()
return errdata
# non-blocking write to stdin of subprocess
def write(self, data):
if self.__process.stdin is None:
raise ValueError("Writing to process with stdin not a pipe")
self.__lock.acquire()
self.__pending_input.append(data)
self.__inputsem.release()
self.__lock.release()
# close stdinput of subprocess
def closeinput(self):
self.__lock.acquire()
self.__quit = True
self.__inputsem.release()
self.__lock.release()
# interface to needed routines in openssl's libcrypto
def _load_crypto_libcrypto():
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
from ctypes.util import find_library
libcrypto = find_library('crypto')
if libcrypto is None:
raise K4MDrmException('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14
c_char_pp = POINTER(c_char_p)
c_int_p = POINTER(c_int)
class AES_KEY(Structure):
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
class LibCrypto(object):
def __init__(self):
self._blocksize = 0
self._keyctx = None
self.iv = 0
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise K4MDrmException('AES improper key used')
return
keyctx = self._keyctx = AES_KEY()
self.iv = iv
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
raise K4MDrmException('Failed to initialize AES key')
def decrypt(self, data):
out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
if rv == 0:
raise K4MDrmException('AES decryption failed')
return out.raw
def keyivgen(self, passwd):
salt = '16743'
saltlen = 5
passlen = len(passwd)
iter = 0x3e8
keylen = 80
out = create_string_buffer(keylen)
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
return out.raw
return LibCrypto
def _load_crypto():
LibCrypto = None
try:
LibCrypto = _load_crypto_libcrypto()
except (ImportError, K4MDrmException):
pass
return LibCrypto
LibCrypto = _load_crypto()
#
# Utility Routines
#
# uses a sub process to get the Hard Drive Serial Number using ioreg
# returns with the first found serial number in that class
def GetVolumeSerialNumber():
cmdline = '/usr/sbin/ioreg -r -c AppleAHCIDiskDriver'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
poll = p.wait('wait')
results = p.read()
reslst = results.split('\n')
sernum = '9999999999'
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('"Serial Number" = "')
if pp >= 0:
sernum = resline[pp+19:]
sernum = sernum[:-1]
sernum = sernum.lstrip()
break
return sernum
# uses unix env to get username instead of using sysctlbyname
def GetUserName():
username = os.getenv('USER')
return username
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
def encode(data, map):
result = ""
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
R = value % len(map)
result += map[Q]
result += map[R]
return result
import hashlib
def SHA256(message):
ctx = hashlib.sha256()
ctx.update(message)
return ctx.digest()
# implements an Pseudo Mac Version of Windows built-in Crypto routine
def CryptUnprotectData(encryptedData):
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
passwdData = encode(SHA256(sp),charMap1)
crp = LibCrypto()
key_iv = crp.keyivgen(passwdData)
key = key_iv[0:32]
iv = key_iv[32:48]
crp.set_decrypt_key(key,iv)
cleartext = crp.decrypt(encryptedData)
return cleartext
# Locate and open the .kindle-info file
def openKindleInfo():
home = os.getenv('HOME')
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = cmdline.encode(sys.getfilesystemencoding())
p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
poll = p1.wait('wait')
results = p1.read()
reslst = results.split('\n')
kinfopath = 'NONE'
cnt = len(reslst)
for j in xrange(cnt):
resline = reslst[j]
pp = resline.find('.kindle-info')
if pp >= 0:
kinfopath = resline
break
if not os.path.exists(kinfopath):
raise K4MDrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r')

View file

@ -0,0 +1,107 @@
# K4PC Windows specific routines
from __future__ import with_statement
import sys, os
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
string_at, Structure, c_void_p, cast
import _winreg as winreg
import traceback
MAX_PATH = 255
kernel32 = windll.kernel32
advapi32 = windll.advapi32
crypt32 = windll.crypt32
#
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
#
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
#
# Exceptions for all the problems that might happen during the script
#
class DrmException(Exception):
pass
class DataBlob(Structure):
_fields_ = [('cbData', c_uint),
('pbData', c_void_p)]
DataBlob_p = POINTER(DataBlob)
def GetSystemDirectory():
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
GetSystemDirectoryW.restype = c_uint
def GetSystemDirectory():
buffer = create_unicode_buffer(MAX_PATH + 1)
GetSystemDirectoryW(buffer, len(buffer))
return buffer.value
return GetSystemDirectory
GetSystemDirectory = GetSystemDirectory()
def GetVolumeSerialNumber():
GetVolumeInformationW = kernel32.GetVolumeInformationW
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
POINTER(c_uint), POINTER(c_uint),
POINTER(c_uint), c_wchar_p, c_uint]
GetVolumeInformationW.restype = c_uint
def GetVolumeSerialNumber(path = GetSystemDirectory().split('\\')[0] + '\\'):
vsn = c_uint(0)
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
return str(vsn.value)
return GetVolumeSerialNumber
GetVolumeSerialNumber = GetVolumeSerialNumber()
def GetUserName():
GetUserNameW = advapi32.GetUserNameW
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
GetUserNameW.restype = c_uint
def GetUserName():
buffer = create_unicode_buffer(32)
size = c_uint(len(buffer))
while not GetUserNameW(buffer, byref(size)):
buffer = create_unicode_buffer(len(buffer) * 2)
size.value = len(buffer)
return buffer.value.encode('utf-16-le')[::2]
return GetUserName
GetUserName = GetUserName()
def CryptUnprotectData():
_CryptUnprotectData = crypt32.CryptUnprotectData
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
c_void_p, c_void_p, c_uint, DataBlob_p]
_CryptUnprotectData.restype = c_uint
def CryptUnprotectData(indata, entropy):
indatab = create_string_buffer(indata)
indata = DataBlob(len(indata), cast(indatab, c_void_p))
entropyb = create_string_buffer(entropy)
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
outdata = DataBlob()
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
None, None, 0, byref(outdata)):
raise DrmException("Failed to Unprotect Data")
return string_at(outdata.pbData, outdata.cbData)
return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData()
#
# Locate and open the Kindle.info file.
#
def openKindleInfo():
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')

View file

@ -39,8 +39,12 @@
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
# 0.17 - added modifications to support its use as an imported python module
# both inside calibre and also in other places (ie K4DeDRM tools)
# 0.17a - disabled the standalone plugin feature since a plugin can not import
# a plugin
__version__ = '0.16'
__version__ = '0.17'
import sys
import struct
@ -248,7 +252,42 @@ class DrmStripper:
def getResult(self):
return self.data_file
if not __name__ == "__main__":
def getUnencryptedBook(infile,pid):
sys.stdout=Unbuffered(sys.stdout)
data_file = file(infile, 'rb').read()
strippedFile = DrmStripper(data_file, pid)
return strippedFile.getResult()
def main(argv=sys.argv):
sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(argv)<4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " %s <infile> <outfile> <PID>" % sys.argv[0]
return 1
else:
infile = argv[1]
outfile = argv[2]
pid = argv[3]
try:
stripped_file = getUnencryptedBook(infile, pid)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
#if not __name__ == "__main__":
if False:
# note a calibre plugin can not import code with another calibre plugin
# in it as it ends up registering two different plugins
from calibre.customize import FileTypePlugin
class MobiDeDRM(FileTypePlugin):
@ -256,7 +295,7 @@ if not __name__ == "__main__":
description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin
version = (0, 1, 6) # The version number of this plugin
version = (0, 1, 7) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
@ -270,41 +309,17 @@ if not __name__ == "__main__":
try:
unlocked_file = DrmStripper(data_file, i).getResult()
except DrmException:
# ignore the error
pass
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
d.show()
d.raise_()
d.exec_()
raise Exception("MobiDeDRM Plugin: Error decoding ebook")
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
d.show()
d.raise_()
d.exec_()
return path_to_ebook
def customization_help(self, gui=False):
return 'Enter PID (separate multiple PIDs with comma)'
if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(sys.argv)<4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " %s <infile> <outfile> <PID>" % sys.argv[0]
sys.exit(1)
else:
infile = sys.argv[1]
outfile = sys.argv[2]
pid = sys.argv[3]
data_file = file(infile, 'rb').read()
try:
strippedFile = DrmStripper(data_file, pid)
file(outfile, 'wb').write(strippedFile.getResult())
except DrmException, e:
print "Error: %s" % e
sys.exit(1)
sys.exit(0)

View file

@ -1,13 +1,4 @@
Kindle_4_Mac_Tools version 1.2
Kindle_4_Mac_Tools_v1.2.zip
http://www.mediafire.com/?8nz9rkg6p9nq23r
New in this release:
- support to identify Topaz Books and print their PID
so that standard Topaz_Tools can be used later if
desired (only works with Kindle Version 1.2.0)
K4MUnswindle
Prerequisites:
@ -26,11 +17,7 @@ Prerequisites:
The directions for use are:
1. double-click to unzip the Kindle_4_Mac_Tools_v1.2.zip
2. open the Kindle_4_Mac_Tools folder
3. double-click on K4Munswindle.pyw
1. double-click on K4Munswindle.pyw
In the window that opens:
@ -44,18 +31,18 @@ In the window that opens:
After a short delay, your Kindle application should open up automagically
In Kindle for Mac:
2. In Kindle for Mac:
- hit the “Home” button to go home.
- double-click on ONE of your books.
This should open the book.
Once the book you want is open
3. Once the book you want is open
- hit the “Home” button and then exit the Kindle for Mac application
Once you have exited the Kindle for Mac application you should see one of the following:
4. Once you have exited the Kindle for Mac application you should see one of the following:
- If the book you selected was a Topaz Book:

View file

@ -0,0 +1,5 @@
README
unswindle can be used to find the book specific PID but it needs to be updated for each version of Kindle4PC that Amazon releases (and therefore is also useful for Linux users who have Wine). This program “patches” the Kindle4PC executable and therefore is very release specific.
Unfortunately unswindle v7 the latest, has not been updated to work with the latest version of Kindle for PC. You will need to find one of the older versions of Kindle4PC and prevent later updates in order to use this tool.

View file

@ -1,41 +0,0 @@
#!/usr/bin/python
#
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# Changelog
# 1.00 - Initial version
__version__ = '1.00'
import sys
import struct
import binascii
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
if __name__ == "__main__":
if len(sys.argv) != 2:
print "Checks Mobipocket PID checksum"
print "Usage:"
print " %s <PID>" % sys.argv[0]
sys.exit(1)
else:
pid = sys.argv[1]
if len(pid) == 8:
pid = checksumPid(pid)
else:
pid = checksumPid(pid[:8])
print pid
sys.exit(0)

View file

@ -1,6 +0,0 @@
- MobiDeDRM.pyw is a simple graphical front end to the mobidedrm.py progam that actually does the DRM removal as long as you know the correct PID to use with it (which depends on the versions of Kindle software used). This is a gui program, simply double-click to launch it.
PIDCHeck.py is a command line python tools to take an 8 digit PID and add the 2 checksum digits to the end to generate a 10 digit PID
All of these scripts are python programs. Python 2.X (32 bit) is already installed in Mac OSX and Linux. We recommend ActiveState's Active Python Version 2.X (32 bit) for Windows users.

View file

@ -1,38 +0,0 @@
Kindle Mobipocket tools 0.2
Copyright (c) 2007, 2009 Igor Skochinsky <skochinsky@mail.ru>
These scripts allow one to read legally purchased Secure Mobipocket books
on Amazon Kindle or Kindle for iPhone.
* kindlepid.py
This script generates Mobipocket PID from the Kindle serial number or iPhone/iPod Touch
identifier (UDID). That PID can then be added at a Mobi retailer site and used for downloading
books locked to the Kindle.
Example:
> kindlepid.py B001BAB012345678
Mobipocket PID calculator for Amazon Kindle. Copyright (c) 2007, 2009 Igor Skochinsky
Kindle 1 serial number detected
Mobipocked PID for Kindle serial# B001BAB012345678 is V176CXM*FZ
* kindlefix.py
This script adds a "CustomDRM" flag necessary for opening Secure
Mobipocket books on Kindle. The book has to be enabled for Kindle's PID
(generated by kindlepid.py). The "fixed" book is written with
extension ".azw". That file can then be uploaded to Kindle for reading.
Example:
> kindlefix.py MyBook.mobi V176CXM*FZ
The Kindleizer v0.2. Copyright (c) 2007, 2009 Igor Skochinsky
Encryption: 2
Mobi publication type: 2
Mobi format version: 4
Found the matching record; setting the CustomDRM flag for Kindle
Output written to MyBook.azw
* History
2007-12-12 Initial release
2009-03-10 Updated scripts to version 0.2
kindlepid.py: Added support for generating PID for iPhone (thanks to mbp)
kindlefix.py: Fixed corrupted metadata issue (thanks to Mark Peek)

View file

@ -0,0 +1,325 @@
#!/usr/bin/python
#
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
# It can run standalone to convert files, or it can be installed as a
# plugin for Calibre (http://calibre-ebook.com/about) so that
# importing files with DRM 'Just Works'.
#
# To create a Calibre plugin, rename this file so that the filename
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
# using its plugin configuration GUI.
#
# Changelog
# 0.01 - Initial version
# 0.02 - Huffdic compressed books were not properly decrypted
# 0.03 - Wasn't checking MOBI header length
# 0.04 - Wasn't sanity checking size of data record
# 0.05 - It seems that the extra data flags take two bytes not four
# 0.06 - And that low bit does mean something after all :-)
# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
# 0.08 - ...and also not in Mobi header version < 6
# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
# import filter it works when importing unencrypted files.
# Also now handles encrypted files that don't need a specific PID.
# 0.11 - use autoflushed stdout and proper return values
# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors
# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace
# and extra blank lines, converted CR/LF pairs at ends of each line,
# and other cosmetic fixes.
# 0.14 - Working out when the extra data flags are present has been problematic
# Versions 7 through 9 have tried to tweak the conditions, but have been
# only partially successful. Closer examination of lots of sample
# files reveals that a confusin has arisen because trailing data entries
# are not encrypted, but it turns out that the multibyte entries
# in utf8 file are encrypted. (Although neither kind gets compressed.)
# This knowledge leads to a simplification of the test for the
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
# 0.17 - added modifications to support its use as an imported python module
# both inside calibre and also in other places (ie K4DeDRM tools)
# 0.17a - disabled the standalone plugin feature since a plugin can not import
# a plugin
__version__ = '0.17'
import sys
import struct
import binascii
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
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
testflags = flags >> 1
while testflags:
if testflags & 1:
num += getSizeOfTrailingDataEntry(ptr, size - num)
testflags >>= 1
# Multibyte data, if present, is included in the encryption, so
# we do not need to check the low bit.
# if flags & 1:
# num += (ord(ptr[size - num - 1]) & 0x3) + 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
if not found_key:
# Then try the default encoding that doesn't require a PID
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and cksum == temp_key_sum:
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])
mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
extra_data_flags = 0
print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
if (mobi_length >= 0xE4) and (mobi_version >= 5):
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
print "Extra Data Flags = %d" %extra_data_flags
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
if crypto_type == 0:
print "This book is not encrypted."
else:
if crypto_type == 1:
raise DrmException("cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("no PIDs found in this file")
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 . . .",
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
if i%100 == 0:
print ".",
# print "record %d, extra_size %d" %(i,extra_size)
new_data += PC1(found_key, data[0:len(data) - extra_size])
if extra_size > 0:
new_data += data[-extra_size:]
#self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data
print "done"
def getResult(self):
return self.data_file
def getUnencryptedBook(infile,pid):
sys.stdout=Unbuffered(sys.stdout)
data_file = file(infile, 'rb').read()
strippedFile = DrmStripper(data_file, pid)
return strippedFile.getResult()
def main(argv=sys.argv):
sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(argv)<4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " %s <infile> <outfile> <PID>" % sys.argv[0]
return 1
else:
infile = argv[1]
outfile = argv[2]
pid = argv[3]
try:
stripped_file = getUnencryptedBook(infile, pid)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
#if not __name__ == "__main__":
if False:
# note a calibre plugin can not import code with another calibre plugin
# in it as it ends up registering two different plugins
from calibre.customize import FileTypePlugin
class MobiDeDRM(FileTypePlugin):
name = 'MobiDeDRM' # Name of the plugin
description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin
version = (0, 1, 7) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import
def run(self, path_to_ebook):
from calibre.gui2 import is_ok_to_use_qt
from PyQt4.Qt import QMessageBox
PID = self.site_customization
data_file = file(path_to_ebook, 'rb').read()
ar = PID.split(',')
for i in ar:
try:
unlocked_file = DrmStripper(data_file, i).getResult()
except DrmException:
if is_ok_to_use_qt():
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
d.show()
d.raise_()
d.exec_()
raise Exception("MobiDeDRM Plugin: Error decoding ebook")
else:
of = self.temporary_file('.mobi')
of.write(unlocked_file)
of.close()
return of.name
def customization_help(self, gui=False):
return 'Enter PID (separate multiple PIDs with comma)'

Some files were not shown because too many files have changed in this diff Show more