2018-09-14 00:48:53 +02:00
|
|
|
#include <getopt.h>
|
2018-07-24 19:56:21 +02:00
|
|
|
#include <stdbool.h>
|
2018-07-24 17:11:24 +02:00
|
|
|
#include <stdio.h>
|
2018-07-24 19:56:21 +02:00
|
|
|
#include <stdlib.h>
|
2018-07-17 14:05:22 +02:00
|
|
|
#include <string.h>
|
2018-07-31 13:18:18 +02:00
|
|
|
#include <unistd.h>
|
2018-07-17 14:05:22 +02:00
|
|
|
|
|
|
|
//Import our code
|
|
|
|
#include "decrypt.h"
|
2018-07-17 17:19:13 +02:00
|
|
|
#include "fileReader.h"
|
2018-07-31 13:18:18 +02:00
|
|
|
#include "inflate.h"
|
2018-07-24 18:10:07 +02:00
|
|
|
#include "utils/min.h"
|
2018-07-17 14:05:22 +02:00
|
|
|
|
2018-08-09 16:20:21 +02:00
|
|
|
#define BUFFER_SIZE 512UL * 1024UL //512 KiB
|
2018-07-24 19:56:21 +02:00
|
|
|
#define ENCRYPTION_HEADER_LENGTH 12UL
|
2018-07-25 02:34:08 +02:00
|
|
|
#define LOCAL_FILE_HEADER_MAGIC (uint32_t)0x04034b50
|
2018-07-24 19:56:21 +02:00
|
|
|
|
2018-08-07 15:22:07 +02:00
|
|
|
uint16_t getUint16(uint8_t* buffer) {
|
2018-07-24 19:56:21 +02:00
|
|
|
return (uint16_t)buffer[0] | \
|
|
|
|
(uint16_t)buffer[1] << 8;
|
|
|
|
}
|
2018-08-07 15:22:07 +02:00
|
|
|
uint32_t getUint32(uint8_t* buffer) {
|
2018-07-24 19:56:21 +02:00
|
|
|
return (uint32_t)buffer[0] | \
|
|
|
|
(uint32_t)buffer[1] << 8 | \
|
|
|
|
(uint32_t)buffer[2] << 16 | \
|
|
|
|
(uint32_t)buffer[3] << 24;
|
|
|
|
}
|
|
|
|
|
2018-09-14 00:48:53 +02:00
|
|
|
struct arguments {
|
|
|
|
/** Path to the disk file that contains the start of the file we want to extract.
|
|
|
|
* The file may stretch across multiple disks though.
|
|
|
|
*/
|
|
|
|
char* diskName;
|
|
|
|
/** Offset into the disk where the file starts. */
|
|
|
|
unsigned long diskOffset;
|
|
|
|
/** Size of the file stored in the disk. */
|
|
|
|
unsigned long fileSize;
|
|
|
|
//Decryption keys
|
|
|
|
bool isEncrypted;
|
2018-09-14 00:56:32 +02:00
|
|
|
//For xdelta3, the location of the old file
|
2018-09-14 00:48:53 +02:00
|
|
|
char* prevFile;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct option long_options[] = {
|
|
|
|
{"disk", required_argument, 0, 'd'},
|
|
|
|
{"offset", required_argument, 0, 'o'},
|
|
|
|
{"size", required_argument, 0, 's'},
|
|
|
|
{"keys", required_argument, 0, 'k'},
|
|
|
|
{"prev", required_argument, 0, 'p'},
|
|
|
|
};
|
|
|
|
|
|
|
|
//Stores current state from command line arguments, initialized to zero
|
|
|
|
struct arguments state = {};
|
|
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
//Parse command line arguments
|
|
|
|
|
|
|
|
int requiredOptions = 0;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
//in this variable, getopt_long stores the current position in the command line args array
|
|
|
|
int option_index = 0;
|
|
|
|
|
2018-09-14 00:56:32 +02:00
|
|
|
int curOption = getopt_long(argc, argv, "d:o:s:k:p:", long_options, &option_index);
|
2018-09-14 00:48:53 +02:00
|
|
|
|
|
|
|
//end of command line arguments reached
|
|
|
|
if (curOption == -1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (curOption) {
|
|
|
|
case 'd': //disk name
|
|
|
|
state.diskName = optarg;
|
|
|
|
requiredOptions |= 1;
|
|
|
|
break;
|
|
|
|
case 'o': //offset
|
|
|
|
state.diskOffset = atol(optarg);
|
|
|
|
requiredOptions |= 2;
|
|
|
|
break;
|
|
|
|
case 's': //size
|
|
|
|
state.fileSize = atol(optarg);
|
|
|
|
requiredOptions |= 4;
|
|
|
|
break;
|
|
|
|
case 'k': { //decryption keys
|
|
|
|
//TODO: parse from optarg
|
|
|
|
uint32_t key0 = atoi(argv[8]);
|
|
|
|
uint32_t key1 = atoi(argv[9]);
|
|
|
|
uint32_t key2 = atoi(argv[10]);
|
|
|
|
//Initialize decryption (pass decryption keys)
|
|
|
|
initDecryptor(key0, key1, key2);
|
|
|
|
state.isEncrypted = true;
|
|
|
|
break;
|
|
|
|
}
|
2018-09-14 00:56:32 +02:00
|
|
|
case 'p': //prev file for xdelta3
|
|
|
|
//TODO
|
|
|
|
break;
|
2018-09-14 00:48:53 +02:00
|
|
|
default:
|
|
|
|
fprintf(stderr, "Unknown option '%c'.", (char)curOption);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (requiredOptions != 7) {
|
|
|
|
fprintf(stderr, "Missing arguments, received %i.", requiredOptions);
|
2018-07-24 19:56:21 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
2018-09-14 00:48:53 +02:00
|
|
|
|
2018-07-24 19:56:21 +02:00
|
|
|
//-------------------------------------------------
|
2018-07-17 14:05:22 +02:00
|
|
|
|
2018-08-07 15:22:07 +02:00
|
|
|
uint8_t* compressedChunk = malloc(BUFFER_SIZE);
|
2018-07-31 13:18:18 +02:00
|
|
|
if (compressedChunk == NULL) {
|
|
|
|
fprintf(stderr, "Could not allocate %lu bytes for compressed buffer.\n", BUFFER_SIZE);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2018-08-07 15:22:07 +02:00
|
|
|
uint8_t* uncompressedChunk = malloc(BUFFER_SIZE);
|
2018-07-31 13:18:18 +02:00
|
|
|
if (uncompressedChunk == NULL) {
|
|
|
|
fprintf(stderr, "Could not allocate %lu bytes for uncompressed buffer.\n", BUFFER_SIZE);
|
|
|
|
exit(1);
|
|
|
|
}
|
2018-08-07 15:22:07 +02:00
|
|
|
memset(uncompressedChunk, (uint8_t)0, BUFFER_SIZE);
|
2018-07-31 13:18:18 +02:00
|
|
|
|
|
|
|
//-------------------------------------------------
|
|
|
|
|
2018-07-25 02:34:08 +02:00
|
|
|
//Initialize file reader
|
2018-09-14 00:48:53 +02:00
|
|
|
initFileReader(state.diskName, state.diskOffset);
|
2018-07-17 15:05:57 +02:00
|
|
|
|
2018-07-24 19:56:21 +02:00
|
|
|
//Skip local file header (30 bytes + additional length)
|
2018-07-31 13:18:18 +02:00
|
|
|
getBytes(compressedChunk, 30UL);
|
2018-07-24 19:56:21 +02:00
|
|
|
//Check that header is correct
|
2018-07-31 13:18:18 +02:00
|
|
|
const uint32_t magic = getUint32(compressedChunk);
|
2018-07-25 02:34:08 +02:00
|
|
|
if (magic != LOCAL_FILE_HEADER_MAGIC) {
|
|
|
|
fprintf(stderr, "Wrong magic in local file header, expected %#010x but found %#010x.", LOCAL_FILE_HEADER_MAGIC, magic);
|
2018-07-24 19:56:21 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
//Read additional length
|
2018-07-31 13:18:18 +02:00
|
|
|
const unsigned long additionalLength = getUint16(compressedChunk + 26) + getUint16(compressedChunk + 28);
|
2018-09-14 00:48:53 +02:00
|
|
|
if (additionalLength > 0UL) {
|
|
|
|
getBytes(NULL, additionalLength);
|
|
|
|
}
|
2018-07-17 15:05:57 +02:00
|
|
|
|
2018-07-24 19:56:21 +02:00
|
|
|
//If file is encrypted, skip 12-byte encryption header
|
2018-09-14 00:48:53 +02:00
|
|
|
if (state.isEncrypted) {
|
2018-07-31 13:18:18 +02:00
|
|
|
getBytes(compressedChunk, ENCRYPTION_HEADER_LENGTH);
|
|
|
|
decrypt(compressedChunk, ENCRYPTION_HEADER_LENGTH);
|
2018-07-24 19:56:21 +02:00
|
|
|
}
|
2018-07-17 15:05:57 +02:00
|
|
|
|
2018-07-24 19:56:21 +02:00
|
|
|
//-------------------------------------------------
|
2018-07-17 15:05:57 +02:00
|
|
|
|
2018-07-31 13:18:18 +02:00
|
|
|
struct InflateOutput inflateResult;
|
|
|
|
inflateInit(compressedChunk, uncompressedChunk, BUFFER_SIZE);
|
|
|
|
|
2018-07-24 18:10:07 +02:00
|
|
|
//Read actual file
|
2018-09-14 00:48:53 +02:00
|
|
|
unsigned long remainingBytes = state.fileSize;
|
2018-07-31 13:18:18 +02:00
|
|
|
bool needToRead = true;
|
2018-07-31 13:38:21 +02:00
|
|
|
bool hasReachedEnd = false;
|
2018-07-31 13:18:18 +02:00
|
|
|
unsigned long chunkSize;
|
2018-08-09 16:20:21 +02:00
|
|
|
unsigned long uncompressedPosition = 0UL;
|
2018-07-31 13:38:21 +02:00
|
|
|
while (remainingBytes > 0 || !hasReachedEnd) {
|
2018-07-31 13:18:18 +02:00
|
|
|
if (needToRead) {
|
2018-08-09 16:20:21 +02:00
|
|
|
chunkSize = min(BUFFER_SIZE - uncompressedPosition, remainingBytes);
|
2018-07-31 13:18:18 +02:00
|
|
|
getBytes(compressedChunk, chunkSize);
|
|
|
|
remainingBytes -= chunkSize;
|
|
|
|
|
|
|
|
//Decrypt file if it is encrypted
|
2018-09-14 00:48:53 +02:00
|
|
|
if (state.isEncrypted) {
|
2018-07-31 13:18:18 +02:00
|
|
|
//TODO: For highest performance, we need to move if condition outside of while loop
|
|
|
|
decrypt(compressedChunk, chunkSize);
|
|
|
|
}
|
2018-07-24 19:56:21 +02:00
|
|
|
}
|
2018-07-24 18:10:07 +02:00
|
|
|
|
|
|
|
//Decompress file
|
2018-07-31 13:18:18 +02:00
|
|
|
//bytes are contained in uncompressedChunk from [0, inflateResult.numBytesWrittenToOutput - 1]
|
|
|
|
inflateResult = inflateInflate(needToRead ? chunkSize : 0, remainingBytes > 0);
|
|
|
|
needToRead = inflateResult.needMoreInput;
|
2018-07-31 13:38:21 +02:00
|
|
|
hasReachedEnd = inflateResult.hasReachedEnd;
|
2018-07-31 13:18:18 +02:00
|
|
|
|
2018-08-07 15:22:07 +02:00
|
|
|
//important: we must not modify uncompressedChunk since miniz may use it as dictionary and read from it during the next invocation of inflateInflate()
|
|
|
|
|
2018-09-14 00:48:53 +02:00
|
|
|
//Write to stdout
|
2018-08-09 16:20:21 +02:00
|
|
|
write(1, uncompressedChunk + uncompressedPosition, inflateResult.numBytesWrittenToOutput);
|
|
|
|
uncompressedPosition += inflateResult.numBytesWrittenToOutput;
|
|
|
|
while (uncompressedPosition >= BUFFER_SIZE) {
|
|
|
|
uncompressedPosition -= BUFFER_SIZE;
|
|
|
|
}
|
2018-07-24 18:10:07 +02:00
|
|
|
|
|
|
|
//Optionally perform xdelta3
|
2018-09-14 00:48:53 +02:00
|
|
|
if (state.prevFile) {
|
2018-09-14 00:56:32 +02:00
|
|
|
//TODO
|
2018-09-14 00:48:53 +02:00
|
|
|
}
|
2018-07-24 18:10:07 +02:00
|
|
|
}
|
2018-07-17 15:05:57 +02:00
|
|
|
|
2018-07-31 13:18:18 +02:00
|
|
|
//release memory
|
|
|
|
free(compressedChunk);
|
|
|
|
free(uncompressedChunk);
|
|
|
|
|
2018-07-17 14:05:22 +02:00
|
|
|
return 0;
|
|
|
|
}
|