// Requires Ndless 2.0 beta from SVN >= r387.
//
// This program is a tool for launching (apriori) arbitrary OS from Nspire OS
// versions supported by Ndless, without installing them.
// I decline any responsibility in users using the program for purposes
// potentially unwanted by TI, if such usages are possible.

#include <os.h>

#define OS_ZIPFILE_PATH "/documents/ndless/phoenix.raw.zip.tns"

//! ZIP local file header format:
typedef struct __attribute__((packed)) {
    uint32_t magic; ///< Local file header signature: 'P', 'K', 0x03, 0x04
    uint16_t minversion; ///< Minimum version needed to extract
    uint16_t flags; ///< General purpose bit flag
    uint16_t method; ///< Compression method
    uint16_t mtime; ///< File last modification time
    uint16_t mdate; ///< File last modification date
    uint32_t crc32; ///< CRC-32
    uint32_t csize; ///< Compressed size
    uint32_t usize; ///< Uncompressed size
    uint16_t namelen; ///< File name length (n)
    uint16_t extralen; ///< Extra field length (m)
} zip_header;

//! ZIP magic in little-endian form.
#define ZIP_MAGIC (0x04034B50) 

void errormsg(const char *message) {
    char title[20];
    char msg[150];
    ascii2utf16(title, "OSLauncher", sizeof(title));
    ascii2utf16(msg, message, sizeof(msg));
    show_dialog_box2(0, title, msg);
}

/** \brief Decompress from file source to file dest until stream ends or EOF.
 * inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be
 * allocated for processing, Z_DATA_ERROR if the deflate data is
 * invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and
 * the version of the library linked do not match, or Z_ERRNO if there
 * is an error reading or writing the files.
 *
 *  Taken from the zpipe.c zlib example.
 *  Modified to write to memory instead of writing to a file.
 */

#define CHUNK 16384

uint8_t * in;
uint8_t * out;

uint32_t inf(FILE *source, uint8_t *dest, uint32_t * crc32_val)
{
    int ret;
    unsigned have;
    z_stream strm;

    /* allocate inflate state */
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = 0;
    strm.next_in = Z_NULL;
    ret = inflateInit2_(&strm, 0xFFFFFFF1, zlibVersion(), sizeof(z_stream));

    if (ret != Z_OK)
        return ret;

    *crc32_val = crc32(0, Z_NULL, 0);

    /* decompress until deflate stream ends or end of file */
    do {
        strm.avail_in = fread(in, 1, CHUNK, source);
        if (ferror(source)) {
            (void)inflateEnd(&strm);
            return Z_ERRNO;
        }
        if (strm.avail_in == 0)
            break;
        strm.next_in = in;

        /* run inflate() on input until output buffer not full */
        do {
            printf(".");
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = inflate(&strm, Z_NO_FLUSH);
            if (ret == Z_STREAM_ERROR) {
                /* state clobbered */
                return ret;
            }
            switch (ret) {
                case Z_NEED_DICT:
                    ret = Z_DATA_ERROR;     /* and fall through */
                case Z_DATA_ERROR:
                case Z_MEM_ERROR:
                    (void)inflateEnd(&strm);
                    return ret;
            }
            have = CHUNK - strm.avail_out;
            memcpy(dest, out, have);
            dest += have;
            *crc32_val = crc32(*crc32_val, out, have);
        } while (strm.avail_out == 0);

        /* done when inflate() says it's done */
    } while (ret != Z_STREAM_END);

    /* clean up and return */
    (void)inflateEnd(&strm);
    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}


int main(void) {

    FILE * ifile;
    uint8_t * dest = NULL;
    size_t read_count;
    uint32_t val;
    zip_header ziphdr;

    ifile = fopen(OS_ZIPFILE_PATH, "rb");
    if (!ifile) {
        errormsg("Can't open OS");
        return 1;
    }

    read_count = fread((void *)&ziphdr, 1, sizeof(ziphdr), ifile);
    if (read_count < sizeof(ziphdr)) {
        errormsg("Error reading OS");
        goto end;
    }

    if (ziphdr.magic != ZIP_MAGIC || ziphdr.method != 0x08 /* DEFLATE */) {
        errormsg("OS must be a ZIP DEFLATE file");
        goto end;
    }

    dest = malloc(ziphdr.usize + 2 * CHUNK);
    if (!dest) {
        errormsg("Can't allocate memory");
        goto end;
    }
    in = dest + ziphdr.usize;
    out = dest + ziphdr.usize + CHUNK;

    //printf("%lX\n%lu\n%lu\n%lu\n%lu\n", ziphdr.crc32, ziphdr.csize, ziphdr.usize, (uint32_t)ziphdr.namelen, (uint32_t)ziphdr.extralen);

    if (fseek(ifile, sizeof(zip_header) + ziphdr.namelen + ziphdr.extralen, SEEK_SET)) {
        errormsg("Error reading OS");
        goto end;
    }

    if (inf(ifile, dest, &val) != Z_OK) {
        errormsg("Error decompressing OS");
        goto end;
    }


    if (val != ziphdr.crc32) {
        errormsg("OS checksum error");
        goto end;
    }

    fclose(ifile);

    // Killing all interrupts is necessary (exercise for the reader: why ?)
    *((uint32_t *)0xDC00000C) = 0xFFFFFFFF;

    __builtin_memcpy((void *)0x10000000, dest, ziphdr.usize);
    // Useful ?
    __builtin_memcpy((void *)0x00000000, (void *)0x10000000, 0x40);

    // This is also necessary (exercise for the reader: why ?)
    *((uint32_t *)0xC000001C) &= ~((1 << 0) | (1 << 11));

    // Launch the OS.
    asm(
    ".arm \n"
    "mov lr, pc\n"
    "ldr pc, =0x10000000\n"
    );
    __builtin_unreachable();

end:
    free(dest);
    fclose(ifile);
    return 1;
}
