/*
** KindleTool, kindle_tool.c
**
** Copyright (C) 2011-2012 Yifan Lu
** Copyright (C) 2012-2018 NiLuJe
** Concept based on an original Python implementation by Igor Skochinsky & Jean-Yves Avenard,
** cf., http://www.mobileread.com/forums/showthread.php?t=63225
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
#include "kindle_tool.h"
#include "kindle_main.h"
#include "kindle_table.h"
#if defined(_WIN32) && !defined(__CYGWIN__)
// NOTE: Handle the rest of the Win32 tempfiles mess in a quick'n dirty way...
// Namely: - We can't use MinGW's mkstemp until 5.0 comes out (the implementation in 4.0.1 unlinks on close, which is unexpected)
// - MSVCRT's tmpfile() creates files in the root drive, which, as we've already mentioned, is a recipe for disaster...
// Whip crude hacks around both of these issues while staying oblivious to the wchar_t potential mess...
// Inspired from fontconfig's compatibility helpers (http://cgit.freedesktop.org/fontconfig/tree/src/fccompat.c)
int kt_win_mkstemp(char *template)
{
if(_mktemp(template) == NULL)
{
fprintf(stderr, "Couldn't create temporary file template: %s.\n", strerror(errno));
return -1;
}
// NOTE: Don't use _O_TEMPORARY, we expect to handle the unlink ourselves!
// NOTE: And while we probably could use _O_NOINHERIT, we do not, for a question of feature parity:
// We don't use O_CLOEXEC on Linux because it depends on Glibc 2.7 & Linux 2.6.23, and we routinely run on stuff much older than that...
return _open(template, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
}
// Inspired from gnulib's tmpfile implementation (http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob;f=lib/tmpfile.c)
FILE *kt_win_tmpfile(void)
{
char template[PATH_MAX];
snprintf(template, PATH_MAX, "%s/%s", kt_tempdir, "/kindletool_tmpfile_XXXXXX");
int fd = -1;
if(_mktemp(template) == NULL)
{
fprintf(stderr, "Couldn't create temporary file template: %s.\n", strerror(errno));
return NULL;
}
fd = _open(template, _O_CREAT | _O_EXCL | _O_TEMPORARY | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
if(fd == -1)
{
fprintf(stderr, "Couldn't open temporary file: %s.\n", strerror(errno));
return NULL;
}
FILE *fp = _fdopen(fd, "w+b");
if(fp != NULL)
return fp;
else
{
// We need to close the fd ourselves in case of error, since our own code expects a FP, not an fd... Which means we have to fudge errno to keep the one from fdopen...
int saved_errno = errno;
_close(fd);
errno = saved_errno;
}
return NULL;
}
#endif
void md(unsigned char *bytes, size_t length)
{
unsigned int i;
for(i = 0; i < length; i++)
{
bytes[i] = (unsigned char)ptog[bytes[i]];
}
}
void dm(unsigned char *bytes, size_t length)
{
unsigned int i;
for(i = 0; i < length; i++)
{
bytes[i] = (unsigned char)gtop[bytes[i]];
}
}
int munger(FILE *input, FILE *output, size_t length, const bool fake_sign)
{
unsigned char bytes[BUFFER_SIZE];
size_t bytes_read;
size_t bytes_written;
while((bytes_read = fread(bytes, sizeof(unsigned char), (length < BUFFER_SIZE && length > 0 ? length : BUFFER_SIZE), input)) > 0)
{
// Don't munge if we asked for a fake package
if(!fake_sign)
md(bytes, bytes_read);
bytes_written = fwrite(bytes, sizeof(unsigned char), bytes_read, output);
if(ferror(output) != 0)
{
fprintf(stderr, "Error munging, cannot write to output: %s.\n", strerror(errno));
return -1;
}
else if(bytes_written < bytes_read)
{
fprintf(stderr, "Error munging, read %zu bytes but only wrote %zu bytes.\n", bytes_read, bytes_written);
return -1;
}
length -= bytes_read;
}
if(ferror(input) != 0)
{
fprintf(stderr, "Error munging, cannot read input: %s.\n", strerror(errno));
return -1;
}
return 0;
}
int demunger(FILE *input, FILE *output, size_t length, const bool fake_sign)
{
unsigned char bytes[BUFFER_SIZE];
size_t bytes_read;
size_t bytes_written;
while((bytes_read = fread(bytes, sizeof(unsigned char), (length < BUFFER_SIZE && length > 0 ? length : BUFFER_SIZE), input)) > 0)
{
// Don't demunge if we supplied a fake package
if(!fake_sign)
dm(bytes, bytes_read);
bytes_written = fwrite(bytes, sizeof(unsigned char), bytes_read, output);
if(ferror(output) != 0)
{
fprintf(stderr, "Error demunging, cannot write to output: %s.\n", strerror(errno));
return -1;
}
else if(bytes_written < bytes_read)
{
fprintf(stderr, "Error demunging, read %zu bytes but only wrote %zu bytes.\n", bytes_read, bytes_written);
return -1;
}
length -= bytes_read;
}
if(ferror(input) != 0)
{
fprintf(stderr, "Error demunging, cannot read input: %s.\n", strerror(errno));
return -1;
}
return 0;
}
const char *convert_device_id(Device dev)
{
switch(dev)
{
case Kindle1:
return "Kindle 1";
case Kindle2US:
return "Kindle 2 US";
case Kindle2International:
return "Kindle 2 International";
case KindleDXUS:
return "Kindle DX US";
case KindleDXInternational:
return "Kindle DX International";
case KindleDXGraphite:
return "Kindle DX Graphite";
case Kindle3WiFi:
return "Kindle 3 WiFi";
case Kindle3WiFi3G:
return "Kindle 3 WiFi+3G";
case Kindle3WiFi3GEurope:
return "Kindle 3 WiFi+3G Europe";
case Kindle4NonTouch:
return "Silver Kindle 4 Non-Touch (2011)";
case Kindle5TouchWiFi:
return "Kindle 5 Touch WiFi";
case Kindle5TouchWiFi3G:
return "Kindle 5 Touch WiFi+3G";
case Kindle5TouchWiFi3GEurope:
return "Kindle 5 Touch WiFi+3G Europe";
case Kindle5TouchUnknown:
return "Kindle 5 Touch (Unknown Variant)";
case Kindle4NonTouchBlack:
return "Black Kindle 4 Non-Touch (2012)";
case KindlePaperWhiteWiFi:
return "Kindle PaperWhite WiFi";
case KindlePaperWhiteWiFi3G:
return "Kindle PaperWhite WiFi+3G";
case KindlePaperWhiteWiFi3GCanada:
return "Kindle PaperWhite WiFi+3G Canada";
case KindlePaperWhiteWiFi3GEurope:
return "Kindle PaperWhite WiFi+3G Europe";
case KindlePaperWhiteWiFi3GJapan:
return "Kindle PaperWhite WiFi+3G Japan";
case KindlePaperWhiteWiFi3GBrazil:
return "Kindle PaperWhite WiFi+3G Brazil";
case KindlePaperWhite2WiFi:
return "Kindle PaperWhite 2 (2013) WiFi";
case KindlePaperWhite2WiFiJapan:
return "Kindle PaperWhite 2 (2013) WiFi Japan";
case KindlePaperWhite2WiFi3G:
return "Kindle PaperWhite 2 (2013) WiFi+3G";
case KindlePaperWhite2WiFi3GCanada:
return "Kindle PaperWhite 2 (2013) WiFi+3G Canada";
case KindlePaperWhite2WiFi3GEurope:
return "Kindle PaperWhite 2 (2013) WiFi+3G Europe";
case KindlePaperWhite2WiFi3GRussia:
return "Kindle PaperWhite 2 (2013) WiFi+3G Russia";
case KindlePaperWhite2WiFi3GJapan:
return "Kindle PaperWhite 2 (2013) WiFi+3G Japan";
case KindlePaperWhite2WiFi4GBInternational:
return "Kindle PaperWhite 2 (2013) WiFi (4GB) International";
case KindlePaperWhite2WiFi3G4GBEurope:
return "Kindle PaperWhite 2 (2013) WiFi+3G (4GB) Europe";
case KindlePaperWhite2Unknown_0xF4:
return "Kindle PaperWhite 2 (2013) (Unknown Variant 0xF4)";
case KindlePaperWhite2Unknown_0xF9:
return "Kindle PaperWhite 2 (2013) (Unknown Variant 0xF9)";
case KindlePaperWhite2WiFi3G4GB:
return "Kindle PaperWhite 2 (2013) WiFi+3G (4GB)";
case KindlePaperWhite2WiFi3G4GBBrazil:
return "Kindle PaperWhite 2 (2013) WiFi+3G (4GB) Brazil";
case KindlePaperWhite2WiFi3G4GBCanada:
return "Kindle PaperWhite 2 (2013) WiFi+3G (4GB) Canada";
case KindleBasic:
return "Kindle Basic (2014)";
case KindleVoyageWiFi:
return "Kindle Voyage WiFi";
case ValidKindleUnknown_0x16:
return "Unknown Kindle (0x16)";
case ValidKindleUnknown_0x21:
return "Unknown Kindle (0x21)";
case KindleVoyageWiFi3G:
return "Kindle Voyage WiFi+3G";
case KindleVoyageWiFi3GJapan:
return "Kindle Voyage WiFi+3G Japan";
case KindleVoyageUnknown_0x4F:
return "Kindle Voyage (Unknown Variant 0x4F)";
case KindleVoyageWiFi3GMexico:
return "Kindle Voyage WiFi+3G Mexico";
case KindleVoyageWiFi3GEurope:
return "Kindle Voyage WiFi+3G Europe";
case ValidKindleUnknown_0x07:
return "Unknown Kindle (0x07)";
case ValidKindleUnknown_0x0B:
return "Unknown Kindle (0x0B)";
case ValidKindleUnknown_0x0C:
return "Unknown Kindle (0x0C)";
case ValidKindleUnknown_0x0D:
return "Unknown Kindle (0x0D)";
case ValidKindleUnknown_0x99:
return "Unknown Kindle (0x99)";
case KindleBasicKiwi:
return "Kindle Basic (2014) Australia";
case KindlePaperWhite3WiFi:
return "Kindle PaperWhite 3 (2015) WiFi";
case KindlePaperWhite3WiFi3G:
return "Kindle PaperWhite 3 (2015) WiFi+3G";
case KindlePaperWhite3WiFi3GMexico:
return "Kindle PaperWhite 3 (2015) WiFi+3G Mexico";
case KindlePaperWhite3WiFi3GEurope:
return "Kindle PaperWhite 3 (2015) WiFi+3G Europe";
case KindlePaperWhite3WiFi3GCanada:
return "Kindle PaperWhite 3 (2015) WiFi+3G Canada";
case KindlePaperWhite3WiFi3GJapan:
return "Kindle PaperWhite 3 (2015) WiFi+3G Japan";
case KindlePaperWhite3WhiteWiFi:
return "White Kindle PaperWhite 3 (2016) WiFi";
case KindlePaperWhite3WhiteWiFi3GJapan:
return "White Kindle PaperWhite 3 (2016) WiFi+3G Japan";
case KindlePW3WhiteUnknown_0KD:
return "White Kindle PaperWhite 3 (Unknown Variant 0KD)";
case KindlePaperWhite3WhiteWiFi3GInternational:
return "White Kindle PaperWhite 3 (2016) WiFi+3G International";
case KindlePaperWhite3WhiteWiFi3GInternationalBis:
return "White Kindle PaperWhite 3 (2016) WiFi+3G International (Bis)";
case KindlePW3WhiteUnknown_0KG:
return "White Kindle PaperWhite 3 (Unknown Variant 0KG)";
case KindlePaperWhite3BlackWiFi32GBJapan:
return "Kindle PaperWhite 3 (2016) WiFi (32GB) Japan";
case KindlePaperWhite3WhiteWiFi32GBJapan:
return "White Kindle PaperWhite 3 (2016) WiFi (32GB) Japan";
case KindleOasisWiFi:
return "Kindle Oasis WiFi";
case KindleOasisWiFi3G:
return "Kindle Oasis WiFi+3G";
case KindleOasisWiFi3GInternational:
return "Kindle Oasis WiFi+3G International";
case KindleOasisUnknown_0GS:
return "Kindle Oasis (Unknown Variant 0GS)";
case KindleOasisUnknown_0GT:
return "Kindle Oasis (Unknown Variant 0GT)";
case KindleOasisWiFi3GEurope:
return "Kindle Oasis WiFi+3G Europe";
case KindleBasic2White:
return "White Kindle Basic 2 (2016)";
case KindleBasic2:
return "Kindle Basic 2 (2016)";
case KindleBasic2Unknown_0DU:
return "Kindle Basic 2 (2016) (Unknown Variant 0DU)";
case KindleOasis2Unknown_0LM:
return "Kindle Oasis 2 (2017) (Unknown Variant 0LM)";
case KindleOasis2Unknown_0LN:
return "Kindle Oasis 2 (2017) (Unknown Variant 0LN)";
case KindleOasis2Unknown_0LP:
return "Kindle Oasis 2 (2017) (Unknown Variant 0LP)";
case KindleOasis2Unknown_0LQ:
return "Kindle Oasis 2 (2017) (Unknown Variant 0LQ)";
case KindleOasis2Unknown_0P1:
return "Kindle Oasis 2 (2017) (Unknown Variant 0P1)";
case KindleOasis2Unknown_0P2:
return "Kindle Oasis 2 (2017) (Unknown Variant 0P2)";
case KindleOasis2Unknown_0P6:
return "Kindle Oasis 2 (2017) (Unknown Variant 0P6)";
case KindleOasis2Unknown_0P7:
return "Kindle Oasis 2 (2017) (Unknown Variant 0P7)";
case KindleOasis2WiFi8GB:
return "Kindle Oasis 2 (2017) WiFi (8GB)";
case KindleOasis2WiFi3G32GB:
return "Kindle Oasis 2 (2017) WiFi+3G (32GB)";
case KindleOasis2WiFi3G32GBEurope:
return "Kindle Oasis 2 (2017) WiFi+3G (32GB) Europe";
case KindleOasis2Unknown_0S3:
return "Kindle Oasis 2 (2017) (Unknown Variant 0S3)";
case KindleOasis2Unknown_0S4:
return "Kindle Oasis 2 (2017) (Unknown Variant 0S4)";
case KindleOasis2Unknown_0S7:
return "Kindle Oasis 2 (2017) (Unknown Variant 0S7)";
case KindleOasis2WiFi32GB:
return "Kindle Oasis 2 (2017) WiFi (32GB)";
case KindleUnknown:
default:
return "Unknown";
}
}
const char *convert_platform_id(Platform plat)
{
switch(plat)
{
case Plat_Unspecified:
return "Unspecified";
case MarioDeprecated:
return "Mario (Deprecated)";
case Luigi:
return "Luigi";
case Banjo:
return "Banjo";
case Yoshi:
return "Yoshi";
case YoshimeProto:
return "Yoshime (Prototype)";
case Yoshime:
return "Yoshime (Yoshime3)";
case Wario:
return "Wario";
case Duet:
return "Duet";
case Heisenberg:
return "Heisenberg";
case Zelda:
return "Zelda";
default:
return "Unknown";
}
}
const char *convert_board_id(Board board)
{
switch(board)
{
case Board_Unspecified:
return "Unspecified";
case Tequila:
return "Tequila";
case Whitney:
return "Whitney";
default:
return "Unknown";
}
}
BundleVersion get_bundle_version(char magic_number[MAGIC_NUMBER_LENGTH])
{
if(!strncmp(magic_number, "FB02", MAGIC_NUMBER_LENGTH) || !strncmp(magic_number, "FB01", MAGIC_NUMBER_LENGTH))
return RecoveryUpdate;
else if(!strncmp(magic_number, "FB03", MAGIC_NUMBER_LENGTH))
return RecoveryUpdateV2;
else if(!strncmp(magic_number, "FC02", MAGIC_NUMBER_LENGTH) || !strncmp(magic_number, "FD03", MAGIC_NUMBER_LENGTH))
return OTAUpdate;
else if(!strncmp(magic_number, "FC04", MAGIC_NUMBER_LENGTH) || !strncmp(magic_number, "FD04", MAGIC_NUMBER_LENGTH) || !strncmp(magic_number, "FL01", MAGIC_NUMBER_LENGTH))
return OTAUpdateV2;
else if(!strncmp(magic_number, "SP01", MAGIC_NUMBER_LENGTH))
return UpdateSignature;
else if(!memcmp(magic_number, "\x1F\x8B\x08\x00", MAGIC_NUMBER_LENGTH)) // GZIP magic number
return UserDataPackage;
else if(!memcmp(magic_number, "\x50\x4B\x03\x04", MAGIC_NUMBER_LENGTH)) // ZIP magic number
return AndroidUpdate;
else
return UnknownUpdate;
}
int md5_sum(FILE *input, char output_string[BASE16_ENCODE_LENGTH(MD5_DIGEST_SIZE)])
{
unsigned char bytes[BUFFER_SIZE];
size_t bytes_read;
struct md5_ctx md5;
uint8_t digest[MD5_DIGEST_SIZE];
md5_init(&md5);
while((bytes_read = fread(bytes, sizeof(unsigned char), BUFFER_SIZE, input)) > 0)
{
md5_update(&md5, bytes_read, bytes);
}
if(ferror(input) != 0)
{
fprintf(stderr, "Error reading input file: %s.\n", strerror(errno));
return -1;
}
md5_digest(&md5, MD5_DIGEST_SIZE, digest);
// And build the hex checksum the nettle way ;)
base16_encode_update(output_string, MD5_DIGEST_SIZE, digest);
return 0;
}
static int kindle_print_help(const char *prog_name)
{
printf(
"usage:\n"
" %s md [ ] [