mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2024-12-25 10:44:35 +06:00
skindle 6 with Topaz support
This commit is contained in:
parent
036eaa683f
commit
2c9852c5d4
@ -1,21 +1,23 @@
|
||||
|
||||
OBJS=skindle.o md5.o sha1.o b64.o
|
||||
OBJS=skindle.o md5.o sha1.o b64.o skinutils.o cbuf.o mobi.o tpz.o
|
||||
|
||||
CC=gcc
|
||||
LD=gcc
|
||||
EXE=skindle
|
||||
EXTRALIBS=-lCrypt32
|
||||
EXTRALIBS=libz.a -lCrypt32 -lAdvapi32
|
||||
CFLAGS=-mno-cygwin
|
||||
|
||||
#use the following to strip your binary
|
||||
LDFLAGS=-s
|
||||
LDFLAGS=-s -mno-cygwin
|
||||
#LDFLAGS=-mno-cygwin
|
||||
|
||||
all: $(EXE)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c $(CFLAGS) $(INC) $< -o $@
|
||||
$(CC) -c $(CFLAGS) -g $(INC) $< -o $@
|
||||
|
||||
$(EXE): $(OBJS)
|
||||
$(LD) $(LDFLAGS) -o $@ $(OBJS) $(EXTRALIBS)
|
||||
$(LD) $(LDFLAGS) -o $@ -g $(OBJS) $(EXTRALIBS)
|
||||
|
||||
clean:
|
||||
-@rm *.o
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
/*
|
||||
Copyright 2010 BartSimpson
|
||||
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.
|
||||
@ -16,13 +16,9 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dependencies: none
|
||||
* build on cygwin: gcc -o skindle skindle.c md5.c sha1.c b64.c -lCrypt32
|
||||
* Under cygwin, you can just type make to build it.
|
||||
* While the binary builds if you use the -mno-cygwin switch, it fails to
|
||||
* work for some reason. 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.
|
||||
* Dependencies: zlib (included)
|
||||
* build on cygwin using make and the included make file
|
||||
* A fully functionaly windows executable is included
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -31,6 +27,7 @@
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -49,7 +46,9 @@
|
||||
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
|
||||
@ -67,12 +66,20 @@ file and the data and algorthims that are used to derive per book
|
||||
PID values.
|
||||
|
||||
Installing:
|
||||
A cygwin compatable binary is included. You need a minimal 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.
|
||||
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 <drm'ed prc file> <name of output file> <kindle.info path>
|
||||
You need to locate your kindle.info file somewhere on your system.
|
||||
You can copy it to a local directory, but you need to refer to it
|
||||
each time you run skindle.
|
||||
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
|
85
skindle/cbuf.c
Normal file
85
skindle/cbuf.c
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
32
skindle/cbuf.h
Normal file
32
skindle/cbuf.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
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
|
BIN
skindle/libz.a
Normal file
BIN
skindle/libz.a
Normal file
Binary file not shown.
365
skindle/mobi.c
Normal file
365
skindle/mobi.c
Normal file
@ -0,0 +1,365 @@
|
||||
/*
|
||||
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;
|
||||
}
|
147
skindle/mobi.h
Normal file
147
skindle/mobi.h
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
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
|
1262
skindle/skindle.c
1262
skindle/skindle.c
File diff suppressed because it is too large
Load Diff
Binary file not shown.
539
skindle/skinutils.c
Normal file
539
skindle/skinutils.c
Normal file
@ -0,0 +1,539 @@
|
||||
/*
|
||||
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\\", ®key) != 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;
|
||||
}
|
100
skindle/skinutils.h
Normal file
100
skindle/skinutils.h
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
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
|
504
skindle/tpz.c
Normal file
504
skindle/tpz.c
Normal file
@ -0,0 +1,504 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
|
82
skindle/tpz.h
Normal file
82
skindle/tpz.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
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
|
332
skindle/zconf.h
Normal file
332
skindle/zconf.h
Normal file
@ -0,0 +1,332 @@
|
||||
/* 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 */
|
1357
skindle/zlib.h
Normal file
1357
skindle/zlib.h
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user