/*
 * tngp/tng.c
 *
 * Copyright (C) 2022 bzt
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * @brief TirNanoG File Format functions
 *
 */

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "tng.h"
#include "zlib.h"
#include "mbedtls/common.h"
#include "mbedtls/sha256.h"

unsigned char tng_mask[65];
extern int verbose;
extern char *main_me;

/**
 * Check a license key
 */
int tng_lickey(char *s, uint32_t enc)
{
    uint64_t v = 0, c;
    unsigned char o[8], mask[32];
    mbedtls_sha256_context sha;
    int i;

    memset(tng_mask, 0, sizeof(tng_mask));
    if(!enc) return 1;
    if(!s) return 0;
    for(i = 0; *s && i < 60; s++, i += 5) {
        if(*s >= 'A' && *s <= 'Z') c = *s - 'A'; else
        if(*s >= '2' && *s <= '7') c = *s - '2' + 26; else
        switch(*s) {
            case '0': c = 'O' - 'A'; break;
            case '1': c = 'L' - 'A'; break;
            case '8': c = 'B' - 'A'; break;
            default: i -= 5; continue;
        }
        v |= c << i;
    }
    memcpy(o, &v, 8);
    o[7] &= 0xF; o[0] ^= o[6] ^ p[3]; o[1] ^= o[4] ^ p[7]; o[2] ^= o[5] ^ p[11]; o[3] ^= o[7] ^ p[19];
    mbedtls_sha256_init(&sha);
    mbedtls_sha256_starts_ret(&sha, 0);
    mbedtls_sha256_update_ret(&sha, o, 4);
    mbedtls_sha256_finish_ret(&sha, mask);
    mbedtls_sha256_free(&sha);
    for(i = 0; i < 32; i++) mask[i] ^= p[256 + i * 4];
    mbedtls_sha256_init(&sha);
    mbedtls_sha256_starts_ret(&sha, 0);
    mbedtls_sha256_update_ret(&sha, mask, 16);
    mbedtls_sha256_finish_ret(&sha, tng_mask);
    mbedtls_sha256_free(&sha);
    mbedtls_sha256_init(&sha);
    mbedtls_sha256_starts_ret(&sha, 0);
    mbedtls_sha256_update_ret(&sha, mask + 16, 16);
    mbedtls_sha256_finish_ret(&sha, tng_mask + 32);
    mbedtls_sha256_free(&sha);
    tng_mask[64] = tng_mask[31] + tng_mask[63];
    for(i = 0; i < 64; i++) tng_mask[i] ^= p[i * 4];
    mbedtls_sha256_init(&sha);
    mbedtls_sha256_starts_ret(&sha, 0);
    mbedtls_sha256_update_ret(&sha, tng_mask, 64);
    mbedtls_sha256_finish_ret(&sha, mask);
    mbedtls_sha256_free(&sha);
    return !memcmp(&mask, &enc, 4);
}

/**
 * Encode double bytes. Use shifting to counteract endianness difference
 */
void tng_rle_enc16(uint16_t *inbuff, int inlen, uint8_t *outbuff, int *outlen)
{
    int i, k, l, o;

    if(!inbuff || inlen < 1 || !outbuff || !outlen) return;

    k = o = 0; outbuff[o++] = 0;
    for(i = 0; i < inlen; i++) {
        for(l = 1; l < 128 && i + l < inlen && inbuff[i] == inbuff[i + l]; l++);
        if(l > 1) {
            l--; if(outbuff[k]) { outbuff[k]--; outbuff[o++] = 0x80 | l; } else outbuff[k] = 0x80 | l;
            outbuff[o++] = inbuff[i] & 0xff; outbuff[o++] = (inbuff[i] >> 8) & 0xff;
            k = o; outbuff[o++] = 0; i += l; continue;
        }
        outbuff[k]++; outbuff[o++] = inbuff[i] & 0xff; outbuff[o++] = (inbuff[i] >> 8) & 0xff;
        if(outbuff[k] > 127) { outbuff[k]--; k = o; outbuff[o++] = 0; }
    }
    if(!(outbuff[k] & 0x80)) { if(outbuff[k]) outbuff[k]--; else o--; }
    *outlen = o;
}

/**
 * Decode double bytes. Use shifting to counteract endianness difference
 */
void tng_rle_dec16(uint8_t *inbuff, int inlen, uint16_t *outbuff, int *outlen)
{
    int l, o = 0;
    unsigned char *end = inbuff + inlen;

    if(!inbuff || inlen < 3 || !outbuff || !outlen) return;

    while(inbuff < end && o < *outlen) {
        l = ((*inbuff++) & 0x7F) + 1;
        if(inbuff[-1] & 0x80) {
            while(l-- && o < *outlen) { outbuff[o] = (inbuff[1] << 8) | inbuff[0]; o++; }
            inbuff += 2;
        } else while(l-- && o < *outlen) { outbuff[o] = (inbuff[1] << 8) | inbuff[0]; o++; inbuff += 2; }
    }
    *outlen = o;
}

/**
 * Check if this player is compatibilibe with the game file
 */
int tng_compat(tng_hdr_t *hdr)
{
    if(verbose > 1)
        printf("%s: checking compatibility: rev %u==0, type %u<=%u, numact %u<=9, numlyr %2u==11, numtrn %u<=5, maxbc %u<=%u\r\n",
            main_me, hdr->revision, (hdr->type & 0x0F), TNG_TYPE_ISO, hdr->numact, hdr->numlyr, hdr->numtrn, hdr->maxbc, TNG_BC_FUNC_MAX);
    return
        hdr->revision == 0 &&           /* file format revision */
        (hdr->type & 0x0F) <= TNG_TYPE_ISO && /* map format */
        hdr->numact <= 9 &&             /* number of user defined actions */
        hdr->numlyr == 11 &&            /* number of map layers */
        hdr->numtrn <= 5 &&             /* number of transportation methods */
        hdr->maxbc <= TNG_BC_FUNC_MAX;  /* highest function code in bytecode */
}

