#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
/*
const char *modelsAndCodes[] = {
	"Lighting / TL4A / MyCloud Ex4",	// 0x00
	"Kings Canyon / KC2A / MyCloud Ex2",	// 0x01
	"Glacier / GLCR / MyCloud Gen 2",	// 0x02
	"",	// 0x03
	"",	// 0x04
};
*/
struct fwHeader
{
    uint32_t kernel_start; // 4 byte = 32 bit = (Address > 0!) = uint32_t = unsigned int
    uint32_t kernel_size;
    uint32_t ramdisk_start;
    uint32_t ramdisk_size;
    uint32_t rootfs_start;
    uint32_t rootfs_size;
    uint32_t config_start;
    uint32_t config_size;
    uint32_t crc_kernel;
    uint32_t crc_ramdisk;
    uint32_t crc_rootfs;
    uint32_t crc_config;
    char delimeter1[2];    // 0x55 0xAA     - WTF!? Sepparator?
    char codename[8];      // Glacier + 0x00
    char delimeter2[2];    // 0x55 0xAA
    char ver_1;         // 0x00 = (int)0	// Product
    char ver_2;         // 0x14 = (int)14	// Custom
    char ver_3;         // 0x02 = (int)2	// Model
						// Found values: 0x00=Ex4 (Lightni), 0x01=Ex2 (KingsCa), 0x02=Gen2 (Glacier)
    char ver_4;         // 0x01 = (int)1	// Hardware
    char ver_5;         // 0x01 = (int)1	// Sub
    char ver_full[55];  // Version string, like: 2.11.140.0321.2016. Maybe more data after this.
    uint32_t header_crc;    // Maybe. Not sure.
    uint32_t next_block;	// Address of header for next data block
};

void GetFile(FILE *source, const char *destName, uint32_t start, uint32_t size) {
	uint32_t stop = start + size;
	char buff[1024];
	long writeBytes = 0, readBytes = 0;

	printf("Unpacking %s... ", destName);

	FILE *output = fopen(destName, "w");
	fseek(source, start, SEEK_SET); // Note: SEEK_SET=0 (Begin of file)

	// Read/write by 1kb blocks
	while (ftell(source) < stop && (size - writeBytes) / sizeof(buff) > 0 && !feof(source))
	{
		readBytes = fread(buff, sizeof(char), sizeof(buff), source);
		fwrite(buff, sizeof(char), readBytes, output);
		writeBytes += readBytes;
	}
	// Read/Write rest (last) block
	readBytes = fread(buff, sizeof(char), (size - writeBytes) % sizeof(buff), source);
	fwrite(buff, sizeof(char), readBytes, output);
	writeBytes += readBytes;

	//writeBytes = ftell(output);
	if (writeBytes = size)
		printf("Done.");
	else
		printf("Error! Size missmatch!");
	printf("\tSize: %ld\n", writeBytes);

	fclose(output);
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage: %s firmware.bin\n", argv[0]);
        return 1;
    }

    printf("\nFirmware file: \"%s\"\n", argv[1]);

    struct fwHeader header;
    FILE *firmwareFile = fopen(argv[1], "rb");
    if (firmwareFile == NULL) {
        printf("Error: Can't open file!\n");
        return 2;
    }

    fread(&header, sizeof(struct fwHeader), 1, firmwareFile);

    // TODO: Check if this file is real firmware? (Correct header?)
    printf("Build product: %s\n", header.codename);
    printf("Build version: %s\n", header.ver_full);
    printf("Model version: %x.%x.%x.%x.%x\n", header.ver_1, header.ver_2, header.ver_3, header.ver_4, header.ver_5);
    printf("Data:\n");
    printf("Section|     Start |          Size |           CRC\n");
    printf("-------|-----------|---------------|--------------\n");
    printf("Kernel \t%10i\t%10i\t%#010x\n", header.kernel_start,  header.kernel_size,  header.crc_kernel);
    printf("Ramdisk\t%10i\t%10i\t%#010x\n", header.ramdisk_start, header.ramdisk_size, header.crc_ramdisk);
    printf("RootFS \t%10i\t%10i\t%#010x\n", header.rootfs_start,  header.rootfs_size,  header.crc_rootfs);
    printf("Config \t%10i\t%10i\t%#010x\n", header.config_start,  header.config_size,  header.crc_config);
    printf("--------------------------------------------------\n");
    printf("\n");
    printf("Extracting files...\n");

    // TODO: Check data: Is files, listen in header > firmwareFile size?
    GetFile(firmwareFile, "uImage.bin",   header.kernel_start,  header.kernel_size);
    GetFile(firmwareFile, "uRamdisk.bin", header.ramdisk_start, header.ramdisk_size);
    GetFile(firmwareFile, "rootfs.cfs",   header.rootfs_start,  header.rootfs_size);
    GetFile(firmwareFile, "config.tgz",   header.config_start,  header.config_size);

    fclose(firmwareFile);
    //fcloseall();    // Close all file streams
    return 0;
}
