/*
 *  Dump a shortcut (lnk) file
 *
 *  Copyright 2005 Mike McCormack
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"
#include "wine/port.h"
#include "winedump.h"

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#include <fcntl.h>
#include <stdarg.h>

#include "windef.h"
#include "winbase.h"

#include "pshpack1.h"

#define SCF_PIDL 1
#define SCF_LOCATION 2
#define SCF_DESCRIPTION 4
#define SCF_RELATIVE 8
#define SCF_WORKDIR 0x10
#define SCF_ARGS 0x20
#define SCF_CUSTOMICON 0x40
#define SCF_UNICODE 0x80
#define SCF_PRODUCT 0x800
#define SCF_COMPONENT 0x1000

typedef struct _LINK_HEADER
{
    DWORD    dwSize;        /* 0x00 size of the header - 0x4c */
    GUID     MagicGuid;     /* 0x04 is CLSID_ShellLink */
    DWORD    dwFlags;       /* 0x14 describes elements following */
    DWORD    dwFileAttr;    /* 0x18 attributes of the target file */
    FILETIME Time1;         /* 0x1c */
    FILETIME Time2;         /* 0x24 */
    FILETIME Time3;         /* 0x2c */
    DWORD    dwFileLength;  /* 0x34 File length */
    DWORD    nIcon;         /* 0x38 icon number */
    DWORD   fStartup;       /* 0x3c startup type */
    DWORD   wHotKey;        /* 0x40 hotkey */
    DWORD   Unknown5;       /* 0x44 */
    DWORD   Unknown6;       /* 0x48 */
} LINK_HEADER, * PLINK_HEADER;

typedef struct tagLINK_ADVERTISEINFO
{
    DWORD size;
    DWORD magic;
    CHAR  bufA[MAX_PATH];
    WCHAR bufW[MAX_PATH];
} LINK_ADVERTISEINFO;

typedef struct _LOCATION_INFO
{
    DWORD  dwTotalSize;
    DWORD  dwHeaderSize;
    DWORD  dwFlags;
    DWORD  dwVolTableOfs;
    DWORD  dwLocalPathOfs;
    DWORD  dwNetworkVolTableOfs;
    DWORD  dwFinalPathOfs;
} LOCATION_INFO;

typedef struct _LOCAL_VOLUME_INFO
{
    DWORD dwSize;
    DWORD dwType;
    DWORD dwVolSerial;
    DWORD dwVolLabelOfs;
} LOCAL_VOLUME_INFO;

typedef struct lnk_string_tag {
    unsigned short size;
    union {
        unsigned short w[1];
        unsigned char a[1];
    } str;
} lnk_string;

#include "poppack.h"

static void guid_to_string(LPGUID guid, char *str)
{
    sprintf(str, "{%08lx-%04x-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X}",
            guid->Data1, guid->Data2, guid->Data3,
            guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
            guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
}

/* the size is a short integer */
static void* load_pidl(int fd)
{
    int r;
    unsigned char *data;
    unsigned short size = 0;

    r = read( fd, &size, sizeof size );
    if (r != sizeof size)
        return NULL;
    if (size<sizeof size)
        return NULL;

    data = malloc(size + sizeof size);
    memcpy(data, &size, sizeof size);
    r = read( fd, data + sizeof size, size );
    if (r != size)
    {
        free(data);
        return NULL;
    }
    return (void*)data;
}

/* size is an integer */
static void* load_long_section(int fd)
{
    int r, size = 0;
    unsigned char *data;

    r = read( fd, &size, sizeof size );
    if (r != sizeof size)
        return NULL;
    if (size<sizeof size)
        return NULL;

    data = malloc(size);
    memcpy(data, &size, sizeof size);
    r = read( fd, data + sizeof size, size - sizeof size);
    if (r != (size - sizeof size))
    {
        free(data);
        return NULL;
    }
    return (void*)data;
}

/* the size is a character count in a short integer */
static lnk_string* load_string(int fd, int unicode)
{
    int r;
    lnk_string *data;
    unsigned short size = 0, bytesize;

    r = read( fd, &size, sizeof size );
    if (r != sizeof size)
        return NULL;
    if ( size == 0 )
        return NULL;

    bytesize = size;
    if (unicode)
        bytesize *= sizeof(WCHAR);
    data = malloc(sizeof *data + bytesize);
    data->size = size;
    if (unicode)
        data->str.w[size] = 0;
    else
        data->str.a[size] = 0;
    r = read(fd, &data->str, bytesize);
    if (r != bytesize)
    {
        free(data);
        return NULL;
    }
    return data;
}


static int dump_pidl(int fd)
{
    lnk_string *pidl;
    int i, n = 0, sz = 0;

    pidl = load_pidl(fd);
    if (!pidl)
        return -1;

    printf("PIDL\n");
    printf("----\n\n");

    while(sz<pidl->size)
    {
        lnk_string *segment = (lnk_string*) &pidl->str.a[sz];

        if(!segment->size)
            break;
        sz+=segment->size;
        if(sz>pidl->size)
        {
            printf("bad pidl\n");
            break;
        }
        n++;
        printf("segment %d (%2d bytes) : ",n,segment->size);
        for(i=0; i<segment->size; i++)
            printf("%02x ",segment->str.a[i]);
        printf("\n");
    }
    printf("\n");

    free(pidl);

    return 0;
}

static void print_unicode_string(const unsigned short *str)
{
    while(*str)
    {
        printf("%c", *str);
        str++;
    }
    printf("\n");
}

static int dump_string(int fd, const char *what, int unicode)
{
    lnk_string *data;

    data = load_string(fd, unicode);
    if (!data)
        return -1;
    printf("%s : ", what);
    if (unicode)
        print_unicode_string(data->str.w);
    else
        printf("%s",data->str.a);
    printf("\n");
    free(data);
    return 0;
}

static int dump_location(int fd)
{
    LOCATION_INFO *loc;
    char *p;

    loc = load_long_section(fd);
    if (!loc)
        return -1;
    p = (char*)loc;

    printf("Location\n");
    printf("--------\n\n");
    printf("Total size    = %ld\n", loc->dwTotalSize);
    printf("Header size   = %ld\n", loc->dwHeaderSize);
    printf("Flags         = %08lx\n", loc->dwFlags);

    /* dump out information about the volume the link points to */
    printf("Volume ofs    = %08lx ", loc->dwVolTableOfs);
    if (loc->dwVolTableOfs && (loc->dwVolTableOfs<loc->dwTotalSize))
    {
        LOCAL_VOLUME_INFO *vol = (LOCAL_VOLUME_INFO *) &p[loc->dwVolTableOfs];

        printf("size %ld  type %ld  serial %08lx  label %ld ",
               vol->dwSize, vol->dwType, vol->dwVolSerial, vol->dwVolLabelOfs);
        if(vol->dwVolLabelOfs)
            printf("(\"%s\")", &p[loc->dwVolTableOfs + vol->dwVolLabelOfs]);
    }
    printf("\n");

    /* dump out the path the link points to */
    printf("LocalPath ofs = %08lx ", loc->dwLocalPathOfs);
    if( loc->dwLocalPathOfs && (loc->dwLocalPathOfs < loc->dwTotalSize) )
        printf("(\"%s\")", &p[loc->dwLocalPathOfs]);
    printf("\n");

    printf("Net Path ofs  = %08lx\n", loc->dwNetworkVolTableOfs);
    printf("Final Path    = %08lx ", loc->dwFinalPathOfs);
    if( loc->dwFinalPathOfs && (loc->dwFinalPathOfs < loc->dwTotalSize) )
        printf("(\"%s\")", &p[loc->dwFinalPathOfs]);
    printf("\n");
    printf("\n");

    free(loc);

    return 0;
}

static const unsigned char table_dec85[0x80] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0xff,0xff,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0xff,
0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0xff,0xff,0xff,0x16,0xff,0x17,
0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0xff,0x34,0x35,0x36,
0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46,
0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0xff,0x53,0x54,0xff,
};

static int base85_to_guid( const char *str, LPGUID guid )
{
    DWORD i, val = 0, base = 1, *p;
    unsigned char ch;

    p = (DWORD*) guid;
    for( i=0; i<20; i++ )
    {
        if( (i%5) == 0 )
        {
            val = 0;
            base = 1;
        }
        ch = str[i];
        if( ch >= 0x80 )
            return 0;
        val += table_dec85[ch] * base;
        if( table_dec85[ch] == 0xff )
            return 0;
        if( (i%5) == 4 )
            p[i/5] = val;
        base *= 85;
    }
    return 1;
}

static int dump_advertise_info(int fd, const char *type)
{
    LINK_ADVERTISEINFO *avt;

    avt = load_long_section(fd);
    if (!avt)
        return -1;

    printf("Advertise Info\n");
    printf("--------------\n\n");
    printf("magic   = %lx\n", avt->magic);
    printf("%s = %s\n", type, avt->bufA);
    if (avt->magic == 0xa0000006)
    {
        char prod_str[40], comp_str[40], feat_str[40];
        char *feat, *comp;
        GUID guid;

        if (base85_to_guid(avt->bufA, &guid))
            guid_to_string( &guid, prod_str );
        else
            strcpy( prod_str, "?" );

        comp = &avt->bufA[20];
        feat = strchr(comp,'>');
        if (!feat)
            feat = strchr(comp,'<');
        if (feat)
        {
            memcpy( comp_str, comp, feat - comp );
            comp_str[feat-comp] = 0;
        }
        else
        {
            strcpy( comp_str, "?" );
        }

        if (feat && feat[0] == '>' && base85_to_guid( &feat[1], &guid ))
            guid_to_string( &guid, feat_str );
        else
            feat_str[0] = 0;

        printf("  product:   %s\n", prod_str);
        printf("  component: %s\n", comp_str );
        printf("  feature:   %s\n", feat_str);
    }
    printf("\n");

    return 0;
}

static int dump_lnk_fd(int fd)
{
    LINK_HEADER *hdr;
    char guid[40];

    hdr = load_long_section( fd );
    if (!hdr)
        return -1;

    guid_to_string(&hdr->MagicGuid, guid);

    printf("Header\n");
    printf("------\n\n");
    printf("Size:    %04lx\n", hdr->dwSize);
    printf("GUID:    %s\n", guid);

    printf("FileAttr: %08lx\n", hdr->dwFileAttr);
    printf("FileLength: %08lx\n", hdr->dwFileLength);
    printf("nIcon: %ld\n", hdr->nIcon);
    printf("Startup: %ld\n", hdr->fStartup);
    printf("HotKey: %08lx\n", hdr->wHotKey);
    printf("Unknown5: %08lx\n", hdr->Unknown5);
    printf("Unknown6: %08lx\n", hdr->Unknown6);

    /* dump out all the flags */
    printf("Flags:   %04lx ( ", hdr->dwFlags);
#define FLAG(x) if(hdr->dwFlags & SCF_##x) printf("%s ",#x);
    FLAG(PIDL)
    FLAG(LOCATION)
    FLAG(DESCRIPTION)
    FLAG(RELATIVE)
    FLAG(WORKDIR)
    FLAG(ARGS)
    FLAG(CUSTOMICON)
    FLAG(UNICODE)
    FLAG(PRODUCT)
    FLAG(COMPONENT)
#undef FLAG
    printf(")\n");

    printf("Length:  %04lx\n", hdr->dwFileLength);
    printf("\n");

    if (hdr->dwFlags & SCF_PIDL)
        dump_pidl(fd);
    if (hdr->dwFlags & SCF_LOCATION)
        dump_location(fd);
    if (hdr->dwFlags & SCF_DESCRIPTION)
        dump_string(fd, "Description", hdr->dwFlags & SCF_UNICODE);
    if (hdr->dwFlags & SCF_RELATIVE)
        dump_string(fd, "Relative path", hdr->dwFlags & SCF_UNICODE);
    if (hdr->dwFlags & SCF_WORKDIR)
        dump_string(fd, "Working directory", hdr->dwFlags & SCF_UNICODE);
    if (hdr->dwFlags & SCF_ARGS)
        dump_string(fd, "Arguments", hdr->dwFlags & SCF_UNICODE);
    if (hdr->dwFlags & SCF_CUSTOMICON)
        dump_string(fd, "Icon path", hdr->dwFlags & SCF_UNICODE);
    if (hdr->dwFlags & SCF_PRODUCT)
        dump_advertise_info(fd, "product");
    if (hdr->dwFlags & SCF_COMPONENT)
        dump_advertise_info(fd, "msi string");

    return 0;
}

int dump_lnk(const char *emf)
{
    int fd;

    fd = open(emf,O_RDONLY);
    if (fd<0)
        return -1;
    dump_lnk_fd(fd);
    close(fd);
    return 0;
}
