ssn-installer/src/main.c

155 lines
4.9 KiB
C

#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//Import our code
#include "utils/min.h"
#include "decrypt.h"
#include "errorAndExit.h"
#include "fileReader.h"
#include "inflate.h"
#include "parseArguments.h"
#include "rateLimiter.h"
#include "xdelta3.h"
//The size of the buffers where the compressed and uncompressed data is stored
#define BUFFER_SIZE 512UL * 1024UL //512 KiB
//If a file is encrypted, there are 12 random bytes before the start of the file contents
#define ENCRYPTION_HEADER_LENGTH 12UL
//Each local file header in a .zip file has the magic bytes "50 4b 03 04" at the beginning
#define LOCAL_FILE_HEADER_MAGIC (uint32_t)0x04034b50
//Convenience functions for reading integers from a char array
uint16_t getUint16(uint8_t* buffer) {
return ((uint16_t)buffer[0]) | \
((uint16_t)buffer[1]) << 8;
}
uint32_t getUint32(uint8_t* buffer) {
return ((uint32_t)buffer[0]) | \
((uint32_t)buffer[1]) << 8 | \
((uint32_t)buffer[2]) << 16 | \
((uint32_t)buffer[3]) << 24;
}
int main(int argc, char *argv[]) {
//Parse command line arguments
struct arguments state = parseArguments(argc, argv);
//-------------------------------------------------
uint8_t* compressedChunk = malloc(BUFFER_SIZE);
if (compressedChunk == NULL) {
fprintf(stderr, "Could not allocate %lu bytes for compressed buffer.\n", BUFFER_SIZE);
errorAndExit();
}
uint8_t* uncompressedChunk = malloc(BUFFER_SIZE);
if (uncompressedChunk == NULL) {
fprintf(stderr, "Could not allocate %lu bytes for uncompressed buffer.\n", BUFFER_SIZE);
errorAndExit();
}
memset(uncompressedChunk, (uint8_t)0, BUFFER_SIZE);
//-------------------------------------------------
//Initialize file reader
initFileReader(state.diskName, state.diskOffset);
//Skip local file header (30 bytes + additional length)
getBytes(compressedChunk, 30UL, false);
//Check that header is correct
const uint32_t magic = getUint32(compressedChunk);
if (magic != LOCAL_FILE_HEADER_MAGIC) {
fprintf(stderr, "Wrong magic in local file header, expected %#010x but found %#010x.\n", LOCAL_FILE_HEADER_MAGIC, magic);
errorAndExit();
}
//Read additional length
const unsigned long additionalLength = getUint16(compressedChunk + 26) + getUint16(compressedChunk + 28);
if (additionalLength > 0UL) {
getBytes(NULL, additionalLength, false);
}
unsigned long remainingBytes = state.fileSize;
//If file is encrypted, skip 12-byte encryption header
if (state.isEncrypted) {
getBytes(compressedChunk, ENCRYPTION_HEADER_LENGTH, false);
decrypt(compressedChunk, ENCRYPTION_HEADER_LENGTH);
remainingBytes -= 12;
}
//Initialize xdelta3
if (state.prevFile) {
xdelta3Init(state.prevFile);
}
//If a target path is specified, create a file write stream
FILE* targetFile;
if (state.target) {
targetFile = fopen(state.target, "wb");
if (!targetFile) {
fprintf(stderr, "Could not open the target file \"%s\" for writing: %s\n", state.target, strerror(errno));
errorAndExit();
}
}
//-------------------------------------------------
struct InflateOutput inflateResult;
inflateInit(compressedChunk, uncompressedChunk, BUFFER_SIZE);
//Read actual file
bool needToRead = true;
bool hasReachedEnd = false;
unsigned long chunkSize;
unsigned long uncompressedPosition = 0UL;
while (remainingBytes > 0 || !hasReachedEnd) {
if (needToRead && remainingBytes > 0) {
chunkSize = min(BUFFER_SIZE, remainingBytes);
getBytes(compressedChunk, chunkSize, chunkSize == remainingBytes);
remainingBytes -= chunkSize;
//Decrypt file if it is encrypted
if (state.isEncrypted) {
decrypt(compressedChunk, chunkSize);
}
}
//Decompress file
//bytes are contained in uncompressedChunk from [0, inflateResult.numBytesWrittenToOutput - 1]
inflateResult = inflateInflate(needToRead ? chunkSize : 0, remainingBytes > 0);
needToRead = inflateResult.needMoreInput;
hasReachedEnd = inflateResult.hasReachedEnd;
//important: we must not modify uncompressedChunk since miniz may use it as dictionary and read from it during the next invocation of inflateInflate()
uint8_t* dataBuffer = uncompressedChunk + uncompressedPosition;
unsigned long numBytes = inflateResult.numBytesWrittenToOutput;
//Optionally perform xdelta3
if (state.prevFile) {
xdelta3AddInput(dataBuffer, numBytes, remainingBytes == 0 && hasReachedEnd, targetFile);
} else { //otherwise write to target file or stdout
consumeCapacity(numBytes);
if (state.target) {
fwrite(dataBuffer, 1, numBytes, targetFile);
} else {
write(1, dataBuffer, numBytes);
}
}
uncompressedPosition += inflateResult.numBytesWrittenToOutput;
while (uncompressedPosition >= BUFFER_SIZE) {
uncompressedPosition -= BUFFER_SIZE;
}
}
//release memory
free(compressedChunk);
free(uncompressedChunk);
return 0;
}