/* zenutils - Utilities for working with creative firmwares.
 * Copyright 2007 (c) Rasmus Ry <rasmus.ry{at}gmail.com>
 *
 * 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 2 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "updater.h"
#include <file.h>
#include <pe.h>
#include <utils.h>


const char* zen::find_firmware_key(const byte* buffer, size_t len)
{
    char szkey1[] = "34d1";
    size_t cchkey1 = strlen(szkey1);
    char szkey2[] = "TbnCboEbn";
    size_t cchkey2 = strlen(szkey2);
    for (int i = 0; i < static_cast<int>(len); i++)
    {
        if (len >= cchkey1)
        {
            if (!strncmp((char*)&buffer[i], szkey1, cchkey1))
            {
                return (const char*)&buffer[i];
            }
        }
        if (len >= cchkey2)
        {
            if (!strncmp((char*)&buffer[i], szkey2, cchkey2))
            {
                return (const char*)&buffer[i];
            }
        }
    }
    return "";
}

dword zen::find_firmware_offset(byte* buffer, size_t len)
{
    for (dword i = 0; i < static_cast<dword>(len); i += 4)
    {
        dword size = *(dword*)&buffer[i];
        if (size != 0
            && buffer[i + 4]     != 0
            && buffer[i + 4 + 1] != 0
            && buffer[i + 4 + 2] != 0
            && buffer[i + 4 + 3] != 0)
        {
            return i;
        }
        if(i > 0xFF) /* Arbitrary guess */
            return 0;
    }
    return 0;
}

bool zen::find_firmware_archive(const std::string& filename, dword& va, dword& pa)
{
    shared::pe_file pef;
    if (!pef.read(filename))
    {
        return false;
    }
    shared::section_info data_section;
    if (!pef.find_section(".data", data_section))
    {
        return false;
    }
    shared::bytes buffer;
    if (!shared::read_file(filename, buffer, data_section.raw_address,
                           data_section.raw_size))
    {
        return false;
    }
    dword offset = find_firmware_offset(&buffer[0], buffer.size());
    if (!offset)
    {
        return false;
    }
    va = data_section.virtual_address + offset;
    pa = data_section.raw_address + offset;

    return true;
}


bool zen::crypt_firmware(const char* key, byte* buffer, size_t len)
{
#if 1
    char key_cpy[255];
    unsigned int i;
    unsigned int tmp = 0;
    int key_length = strlen(key);
    
    for(i=0; i < strlen(key); i++)
        key_cpy[i] = key[i] - 1;
    
    for(i=0; i < len; i++)
    {
        buffer[i] ^= key_cpy[tmp] | 0x80;
        tmp = (tmp + 1) % key_length;
    }
    
    return true;
#else
    /* Determine if the key length is dword aligned. */
    int keylen = strlen(key);
    int keylen_rem = keylen % sizeof(dword);

    /* Determine how many times the key must be repeated to be dword aligned. */
    int keycycle = keylen_rem ? (sizeof(dword) / keylen_rem) : 1;
    int keyscount = (keylen * keycycle) / sizeof(dword);

    /* Allocate a buffer to hold the key as an array of dwords. */
    dword* keys = new dword[keyscount];

    /* Copy the key into the key array, whilst mutating it. */
    for (int i = 0; i < keyscount; i++)
    {
        dword val;
        int keyoffset = (i * sizeof(dword)) % keylen;
        if ((keyoffset+sizeof(dword)) < keylen)
        {
            val = *(dword*)&key[keyoffset];
        }
        else
        {
            val = key[keyoffset]
                | (key[(keyoffset + 1) % keylen] << 8)
                | (key[(keyoffset + 2) % keylen] << 16)
                | (key[(keyoffset + 3) % keylen] << 24);
        }
        keys[i] = (val - 0x01010101) | 0x80808080;
    }

    /* Determine the number of dwords in the buffer. */
    int len_div = len / sizeof(dword);

    /* Decrypt all dwords of the buffer. */
    for (int i = 0; i < len_div; i++)
    {
        ((dword*)buffer)[i] ^= keys[i % keyscount];
    }

    /* Determine the remaining number of bytes in the buffer. */
    int len_rem = len % sizeof(dword);

    /* Decrypt the remaining number of bytes in the buffer. */
    for (int i = len_div * sizeof(dword); i < len; i++)
    {
        buffer[i] ^= ((key[i % keylen] - 0x01) | 0x80);
    }

    return true;
#endif
}
