From 2c9852c5d46bc0a09feecd800cdff8a98edd4312 Mon Sep 17 00:00:00 2001 From: Bart Simpson Date: Sat, 2 Jan 2010 21:21:06 +0000 Subject: [PATCH] skindle 6 with Topaz support --- skindle/Makefile | 12 +- skindle/README.txt | 39 +- skindle/cbuf.c | 85 +++ skindle/cbuf.h | 32 + skindle/libz.a | Bin 0 -> 81176 bytes skindle/mobi.c | 365 ++++++++++++ skindle/mobi.h | 147 +++++ skindle/skindle.c | 1262 +++++++++++----------------------------- skindle/skindle.exe | Bin 22016 -> 62976 bytes skindle/skinutils.c | 539 +++++++++++++++++ skindle/skinutils.h | 100 ++++ skindle/tpz.c | 504 ++++++++++++++++ skindle/tpz.h | 82 +++ skindle/zconf.h | 332 +++++++++++ skindle/zlib.h | 1357 +++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 3915 insertions(+), 941 deletions(-) create mode 100644 skindle/cbuf.c create mode 100644 skindle/cbuf.h create mode 100644 skindle/libz.a create mode 100644 skindle/mobi.c create mode 100644 skindle/mobi.h create mode 100644 skindle/skinutils.c create mode 100644 skindle/skinutils.h create mode 100644 skindle/tpz.c create mode 100644 skindle/tpz.h create mode 100644 skindle/zconf.h create mode 100644 skindle/zlib.h diff --git a/skindle/Makefile b/skindle/Makefile index 2eb8f3c..d4f6938 100644 --- a/skindle/Makefile +++ b/skindle/Makefile @@ -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 diff --git a/skindle/README.txt b/skindle/README.txt index 132844c..bf0a889 100644 --- a/skindle/README.txt +++ b/skindle/README.txt @@ -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: * \...\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 -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 -o [-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 \ No newline at end of file diff --git a/skindle/cbuf.c b/skindle/cbuf.c new file mode 100644 index 0000000..60112e2 --- /dev/null +++ b/skindle/cbuf.c @@ -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 +#include +#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; + } +} + diff --git a/skindle/cbuf.h b/skindle/cbuf.h new file mode 100644 index 0000000..738dfd2 --- /dev/null +++ b/skindle/cbuf.h @@ -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 diff --git a/skindle/libz.a b/skindle/libz.a new file mode 100644 index 0000000000000000000000000000000000000000..0a2e3b5d9071a337e9b2a1da14307b59cef39970 GIT binary patch literal 81176 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 GNB#j(`UH&t literal 0 HcmV?d00001 diff --git a/skindle/mobi.c b/skindle/mobi.c new file mode 100644 index 0000000..25de87c --- /dev/null +++ b/skindle/mobi.c @@ -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 +#include +#include +#include +#include + +#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; +} diff --git a/skindle/mobi.h b/skindle/mobi.h new file mode 100644 index 0000000..61758c6 --- /dev/null +++ b/skindle/mobi.h @@ -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 +#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 diff --git a/skindle/skindle.c b/skindle/skindle.c index ee669b9..f9637aa 100644 --- a/skindle/skindle.c +++ b/skindle/skindle.c @@ -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. @@ -17,12 +17,13 @@ /* * Dependencies: none - * build on cygwin: gcc -o skindle skindle.c md5.c sha1.c b64.c -lCrypt32 + * 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. - * 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. + * 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. */ /* @@ -67,973 +68,394 @@ #include #include -//If you prefer to use openssl uncomment the following -//#include -//#include +#include "skinutils.h" +#include "cbuf.h" +#include "mobi.h" +#include "tpz.h" -//If you prefer to use openssl remove the following 2 line -#include "md5.h" -#include "sha1.h" +#include "zlib.h" -unsigned int base64(unsigned char *inbuf, unsigned int len, unsigned char *outbuf); +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); -/* 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 + 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); - * kindle.info layout + keysRecord = getMetadata(topaz, "keys"); + if (keysRecord == NULL) { + //fail + } + keysRecordRecord = getMetadata(topaz, keysRecord); + if (keysRecordRecord == NULL) { + //fail + } - * Key:AbaZZ6z4a7ZxzLzkZcaqauZMZjZ_Ztz6 ("kindle.account.tokens") - * Value: mazama64Encode(CryptProtectData(some sha1 hash)) + pid = getBookPid(keysRecord, strlen(keysRecord), keysRecordRecord, strlen(keysRecordRecord)); - * 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)) + 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)) */ -typedef struct _SimpleMapNode { - char *key; - char *value; -} SimpleMapNode; - -typedef struct _MapList { - SimpleMapNode *node; - struct _MapList *next; -} MapList; - -MapList *kindleMap; - -#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; - - -//#define bswap_l ntohl -//#define bswap_s ntohs - -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; -} - -MapList *findNode(char *key) { - MapList *l; - for (l = kindleMap; l; l = l->next) { - if (strcmp(key, l->node->key) == 0) { - return l; - } + // + // Decrypt book key + // + + Payload *dkey = getBookPayloadRecord(topaz, "dkey", 0, 0); + + if (dkey == NULL) { + fprintf(stderr, "No dkey record found\n"); + freeTopazFile(topaz); + return 0; } - return NULL; -} - -void addMapNode(char *key, char *value) { - MapList *ml = findNode(key); - if (ml) { - free(ml->node->value); - ml->node->value = value; + + if (pid) { + topaz->bookKey = decryptDkeyRecords(dkey, pid); + free(pid); } - else { - SimpleMapNode *smn = (SimpleMapNode*)malloc(sizeof(SimpleMapNode)); - smn->key = key; - smn->value = value; - ml = (MapList*)malloc(sizeof(MapList)); - ml->node = smn; - ml->next = kindleMap; - kindleMap = ml; - } -} - -void dumpMap() { - MapList *l; - for (l = kindleMap; l; l = l->next) { - fprintf(stderr, "%s:%s\n", l->node->key, l->node->value); - } -} - -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; - addMapNode(key, value); - } -} - -int buildKindleMap(char *infoFile) { - int result = 0; - struct stat statbuf; - 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); + 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; } } - else { - fprintf(stderr, "short read on info file\n"); + if (topaz->bookKey == NULL) { + fprintf(stderr, "No valid pids available, failed to find DRM key\n"); + freeTopazFile(topaz); + freePayload(dkey); + return 0; } - free(infoBuf); - fclose(fd); - return 1; } - return 0; -} - -png_crc_table_init(unsigned int *crc_table) { - unsigned int i; - 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 compute_png_crc(unsigned char *input, unsigned int len, unsigned int *crc_table) { - unsigned int crc = 0; - unsigned int i; - 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; - png_crc_table_init(crc_table); - crc = bswap_l(compute_png_crc(input, len, crc_table)); - 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; + + fprintf(stderr, "Found a DRM key!\n"); for (i = 0; i < 8; i++) { - unsigned char v = output[i]; - output[i] = decodeString[((((v >> 5) & 3) ^ v) & 0x1F) + (v >> 7)]; + 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; } -char *string_20 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"; -char *string_40 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"; - -char *mazamaEncode(unsigned char *input, unsigned int len, unsigned char choice) { - unsigned int i; - char *enc, *out; - if (choice == 0x20) enc = string_20; - else if (choice == 0x40) enc = string_40; - 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_20[i]) { - dec = string_20; - break; - } - } - if (dec == NULL) { - for (i = 0; i < 4 && i < len; i++) { - if (*input == string_40[i]) { - dec = string_40; - 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; -} - -unsigned char *getExthData(ExthHeader *eh, unsigned int type, unsigned int *len) { - unsigned int i; - unsigned int exthRecords = bswap_l(eh->recordCount); - ExthRecHeader *erh = eh->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; -} - -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; - -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; -} - -#ifndef HEADER_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) { - MD5_CTX s; - MD5_Init(&s); - MD5_Update(&s, in, len); - MD5_Final(md, &s); -} -#endif - -#ifndef HEADER_SHA_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) { - SHA_CTX s; - SHA1_Init(&s); - SHA1_Update(&s, in, len); - SHA1_Final(md, &s); -} -#endif - -int main(int argc, char **argv) { +int processMobi(FILE *prc, char *outFile, PidList *extraPids) { //had to pile all these up here to please VS2009 PDB header; - struct stat statbuf; - FILE *prc, *out; - HeaderRec *hr; - PalmDocHeader *pdh; - MobiHeader *mobi; - ExthHeader *exth; - long record0_offset; - unsigned int record0_size, mobiLen, extra_data_flags; - unsigned int recs, i, drmCount, len209; - unsigned char *record0, *d209; - unsigned char *vsn, *username, *mrn_key, *kat_key; - 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]; - unsigned char pid_base64[SHA_DIGEST_LENGTH * 2]; - unsigned int outl; - SHA_CTX sha1_ctx; - MapList *ml; + cbuf *keyBuf; + char *pid; + FILE *out; + unsigned int i, keyPtrLen; + unsigned char *keyPtr; unsigned int drmOffset, drm_len; - unsigned char *drm, *found_key; - - if (argc != 4) { - fprintf(stderr, "usage: %s\n ", argv[0]); - exit(1); - } - - if (stat(argv[1], &statbuf) != 0) { - fprintf(stderr, "Unable to stat %s, quitting\n", argv[1]); - exit(1); - } - - prc = fopen(argv[1], "rb"); - if (prc == NULL) { - fprintf(stderr, "%s bad open, quitting\n", argv[1]); - exit(1); - } - - if (fread(&header, sizeof(header), 1, prc) != 1) { - fprintf(stderr, "%s bad header read, quitting\n", argv[1]); - fclose(prc); - exit(1); - } - - //do BOOKMOBI check - if (header.type != 0x4b4f4f42 || header.creator != 0x49424f4d) { - fprintf(stderr, "Invalid header type or creator, quitting\n"); - fclose(prc); - exit(1); - } - - if (!buildKindleMap(argv[3])) { - fprintf(stderr, "buildMap failed\n"); - fclose(prc); - exit(1); - } - - recs = bswap_s(header.numRecs); - - hr = (HeaderRec*)malloc(recs * sizeof(HeaderRec)); - if (fread(hr, sizeof(HeaderRec), recs, prc) != recs) { - fprintf(stderr, "Failed read of header record, quitting\n"); - fclose(prc); - exit(1); - } - - record0_offset = bswap_l(hr[0].offset); - record0_size = bswap_l(hr[1].offset) - record0_offset; - - if (fseek(prc, record0_offset, SEEK_SET) == -1) { - fprintf(stderr, "bad seek to header record offset, quitting\n"); - fclose(prc); - exit(1); - } - - record0 = (unsigned char*)malloc(record0_size); - - if (fread(record0, record0_size, 1, prc) != 1) { - fprintf(stderr, "bad read of record0, quitting\n"); - free(record0); - fclose(prc); - exit(1); - } - - pdh = (PalmDocHeader*)record0; - if (bswap_s(pdh->encryptionType) != 2) { - fprintf(stderr, "MOBI BOOK is not encrypted, quitting\n"); - free(record0); - fclose(prc); - exit(1); - } - - mobi = (MobiHeader*)(pdh + 1); - if (mobi->id != 0x49424f4d) { - fprintf(stderr, "MOBI header not found, quitting\n"); - free(record0); - fclose(prc); - exit(1); - } - - mobiLen = bswap_l(mobi->hdrLen); - extra_data_flags = 0; + unsigned char *drm, *found_key = NULL; + MobiFile *book; + int result; - if (mobiLen >= 0xe4) { - extra_data_flags = bswap_s(mobi->extra_flags); + book = parseMobiHeader(prc); + + if (book == NULL) { + fprintf(stderr, "Failed to read mobi headers\n"); + return 0; } - if ((bswap_l(mobi->exthFlags) & 0x40) == 0) { - fprintf(stderr, "Missing exth header, quitting\n"); - free(record0); - fclose(prc); - exit(1); - } + book->pids = extraPids; + keyPtr = getExthData(book, 209, &keyPtrLen); - exth = (ExthHeader*)(mobiLen + (char*)mobi); - if (exth->id != 0x48545845) { - fprintf(stderr, "EXTH header not found\n"); - free(record0); - fclose(prc); - exit(1); - } - - //if you want a list of EXTH records, uncomment the following -// enumExthRecords(exth); - - drmCount = bswap_l(mobi->drmCount); - - if (drmCount == 0) { - fprintf(stderr, "no PIDs found in this file, quitting\n"); - free(record0); - fclose(prc); - exit(1); - } - - if (GetUserName(name, &nlen) == 0) { - fprintf(stderr, "GetUserName failed, quitting\n"); - fclose(prc); - exit(1); - } - 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); - - ml = findNode(mrn_key); - if (ml) { - DATA_BLOB DataIn; - DATA_BLOB DataOut; - DataIn.pbData = mazamaDecode(ml->node->value, (int*)&DataIn.cbData); - if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) { - char *devId = (char*)malloc(DataOut.cbData + 2 * 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(record0); - fclose(prc); - exit(1); - } - - free(DataIn.pbData); - } - - ml = findNode(kat_key); - if (ml) { - DATA_BLOB DataIn; - DATA_BLOB DataOut; - DataIn.pbData = mazamaDecode(ml->node->value, (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"); - fclose(prc); - free(record0); - exit(1); - } - - free(DataIn.pbData); - } - - d209 = getExthData(exth, 209, &len209); - - if (d209 != NULL) { - unsigned char *rec; + keyBuf = b_new(128); + if (keyPtr != NULL) { unsigned int idx; - SHA1_Update(&sha1_ctx, d209, len209); - for (idx = 0; idx < len209; idx += 5) { + for (idx = 0; idx < keyPtrLen; idx += 5) { + unsigned char *rec; unsigned int dlen; - unsigned int rtype = bswap_l(*(unsigned int*)(d209 + idx + 1)); - rec = getExthData(exth, rtype, &dlen); + unsigned int rtype = bswap_l(*(unsigned int*)(keyPtr + idx + 1)); + rec = getExthData(book, rtype, &dlen); if (rec != NULL) { -// fprintf(stderr, "exth %d: %s\n", rtype, rec); - SHA1_Update(&sha1_ctx, rec, dlen); + b_add_buf(keyBuf, rec, dlen); } } } - SHA1_Final(sha1sum, &sha1_ctx); + pid = getBookPid(keyPtr, keyPtrLen, keyBuf->buf, keyBuf->idx); - outl = base64(sha1sum, SHA_DIGEST_LENGTH, pid_base64); + b_free(keyBuf); - pid_base64[8] = 0; - fprintf(stderr, "PID for %s is: %s\n", header.name, pid_base64); + 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)) */ - free(mrn_key); - free(kat_key); - free(vsn); - free(username); + drmOffset = bswap_l(book->mobi->drmOffset); - drmOffset = bswap_l(mobi->drmOffset); + drm_len = bswap_l(book->mobi->drmSize); + drm = book->record0 + drmOffset; - drm_len = bswap_l(mobi->drmSize); - drm = record0 + drmOffset; - - found_key = parseDRM(drm, drmCount, pid_base64, 8); - if (found_key) { - fprintf(stderr, "Found a DRM key!\n"); + if (pid) { + found_key = parseDRM(drm, book->drmCount, pid, 8); + free(pid); } - else { - fprintf(stderr, "Failed to find DRM key\n"); - free(record0); - fclose(prc); - exit(1); + 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"); - // kill the drm keys - memset(record0 + drmOffset, 0, drm_len); - // kill the drm pointers - mobi->drmOffset = 0xffffffff; - mobi->drmCount = mobi->drmSize = mobi->drmFlags = 0; - // clear the crypto type - pdh->encryptionType = 0; - - out = fopen(argv[2], "wb"); + out = fopen(outFile, "wb"); if (out == NULL) { fprintf(stderr, "Failed to open output file, quitting\n"); - free(record0); - fclose(prc); + 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 -o [-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); } - fwrite(&header, sizeof(header), 1, out); - fwrite(hr, sizeof(HeaderRec), recs, out); - fwrite("\x00\x00", 1, 2, out); - fwrite(record0, record0_size, 1, out); - - //need to zero out exth 209 data - for (i = 1; i < recs; i++) { - unsigned int offset = bswap_l(hr[i].offset); - unsigned int len, extra_size; - unsigned char *rec; - if (i == (recs - 1)) { //last record extends to end of file - len = statbuf.st_size - offset; - } - else { - len = bswap_l(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(prc, offset, SEEK_SET) != 0) { - fprintf(stderr, "Failed record seek on input, quitting\n"); - free(record0); - free(rec); - fclose(prc); - fclose(out); - _unlink(argv[2]); - exit(1); - } - if (fread(rec, len, 1, prc) != 1) { - fprintf(stderr, "Failed record read on input, quitting\n"); - free(record0); - free(rec); - fclose(prc); - fclose(out); - _unlink(argv[2]); - exit(1); - } - - extra_size = getSizeOfTrailingDataEntries(rec, len, extra_data_flags); - PC1(found_key, 16, rec, rec, len - extra_size, 1); - fwrite(rec, len, 1, out); - free(rec); - } - fprintf(stderr, "Done! Enjoy!\n"); - -/* //The following loop dumps the contents of your kindle.info file - for (ml = kindleMap; ml; ml = ml->next) { - DATA_BLOB DataIn; - DATA_BLOB DataOut; - DataIn.pbData = mazamaDecode(ml->node->value, (int*)&DataIn.cbData); - if (CryptUnprotectData(&DataIn, NULL, NULL, NULL, NULL, 1, &DataOut)) { - fprintf(stderr, "CryptUnprotectData success for key: %s\n", ml->node->key); - fwrite(DataOut.pbData, DataOut.cbData, 1, stderr); - fprintf(stderr, "\n"); - LocalFree(DataOut.pbData); + 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); } - else { - fprintf(stderr, "CryptUnprotectData failed\n"); - } - free(DataIn.pbData); } -*/ - fclose(prc); - fclose(out); - free(record0); + 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; } diff --git a/skindle/skindle.exe b/skindle/skindle.exe index 3e47d1b26f138b51b519b51ad978b0ab3b3bb11f..3dc187f56c83686db493630c4e2180a5b7a4a926 100644 GIT binary patch literal 62976 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 literal 22016 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 +#include +#include +#include +#include +#include + +#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; +} diff --git a/skindle/skinutils.h b/skindle/skinutils.h new file mode 100644 index 0000000..4b88c6d --- /dev/null +++ b/skindle/skinutils.h @@ -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 +//#include + +#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 diff --git a/skindle/tpz.c b/skindle/tpz.c new file mode 100644 index 0000000..564f31a --- /dev/null +++ b/skindle/tpz.c @@ -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 +#include +#include + +#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); +} + diff --git a/skindle/tpz.h b/skindle/tpz.h new file mode 100644 index 0000000..4138171 --- /dev/null +++ b/skindle/tpz.h @@ -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 +#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 diff --git a/skindle/zconf.h b/skindle/zconf.h new file mode 100644 index 0000000..03a9431 --- /dev/null +++ b/skindle/zconf.h @@ -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 + /* 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 /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* 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 */ diff --git a/skindle/zlib.h b/skindle/zlib.h new file mode 100644 index 0000000..0228179 --- /dev/null +++ b/skindle/zlib.h @@ -0,0 +1,1357 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.3, July 18th, 2005 + + Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + + 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. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.2.3" +#define ZLIB_VERNUM 0x1230 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumualte before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + the value returned by deflateBound (see below). If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, + Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() stop + if and when it gets to the next deflate block boundary. When decoding the + zlib or gzip format, this will cause inflate() to return immediately after + the header and before the first block. When doing a raw inflate, inflate() + will go ahead and process the first block, and will return when it gets to + the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 + if inflate() is currently decoding the last block in the deflate stream, + plus 128 if inflate() returned immediately after decoding an end-of-block + code or decoding the complete header up to just before the first byte of the + deflate stream. The end-of-block will not be indicated until all of the + uncompressed data from that block has been written to strm->next_out. The + number of unused bits may in general be greater than seven, except when + bit 7 of data_type is set, in which case the number of unused bits will be + less than eight. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster approach + may be used for the single inflate() call. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the only effect of the flush parameter in this implementation + is on the return value of inflate(), as noted below, or when it returns early + because Z_BLOCK is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the adler32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the adler32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() will decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically. Any information + contained in the gzip header is not retained, so applications that need that + information should instead use raw inflate, see inflateInit2() below, or + inflateBack() and perform their own processing of the gzip header and + trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may then + call inflateSync() to look for a good compression block if a partial recovery + of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), + no header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as + Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy + parameter only affects the compression ratio but not the correctness of the + compressed output even if it is not set appropriately. Z_FIXED prevents the + use of dynamic Huffman codes, allowing for a simpler decoder for special + applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. In addition, the + current implementation of deflate will use at most the window size minus + 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() + or deflateInit2(). This would be used to allocate an output buffer + for deflation in a single pass, and so would be called before deflate(). +*/ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the + bits leftover from a previous deflate stream when appending to it. As such, + this function can only be used for raw deflate, and must be used before the + first deflate() call after a deflateInit2() or deflateReset(). bits must be + less than or equal to 16, and that many of the least significant bits of + value will be inserted in the output. + + deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is + a crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg + is set to null if there is no error message. inflateInit2 does not perform + any decompression apart from reading the zlib header if present: this will + be done by inflate(). (So next_in and avail_in may be modified, but next_out + and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called + immediately after inflateInit2() or inflateReset() and before any call of + inflate() to set the dictionary. The application must insure that the + dictionary that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK can be used to + force inflate() to return immediately after header processing is complete + and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When + any of extra, name, or comment are not Z_NULL and the respective field is + not present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the paramaters are invalid, Z_MEM_ERROR if the internal state could not + be allocated, or Z_VERSION_ERROR if the version of the library does not + match the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is more efficient than inflate() for + file i/o applications in that it avoids copying between the output and the + sliding window by simply making the window itself the output buffer. This + function trusts the application to not change the output buffer passed by + the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free + the allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects + only the raw deflate stream to decompress. This is different from the + normal behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format + error in the deflate stream (in which case strm->msg is set to indicate the + nature of the error), or Z_STREAM_ERROR if the stream was not properly + initialized. In the case of Z_BUF_ERROR, an input or output error can be + distinguished using strm->next_in which will be Z_NULL only if in() returned + an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to + out() returning non-zero. (in() will always be called before out(), so + strm->next_in is assured to be defined if out() returns non-zero.) Note + that inflateBack() cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least the value returned + by compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before + a compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h", or 'R' for run-length encoding + as in "wb1R". (See the description of deflateInit2 for more information + about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). The number of + uncompressed bytes written is limited to 4095. The caller should assure that + this limit is not exceeded. If it is exceeded, then gzprintf() will return + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read again later. + Only one character of push-back is allowed. gzungetc() returns the + character pushed, or -1 on failure. gzungetc() will fail if a + character has been pushed but not read yet, or if c is -1. The pushed + character will be discarded if the stream is repositioned with gzseek() + or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns 1 if file is being read directly without decompression, otherwise + zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is NULL, this function returns the required initial + value for the for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +} +#endif + +#endif /* ZLIB_H */