/*
 *
 * Tests Windows DirectShow SourceFilter DLL output
 *
 * $Id: srcfilter.c,v 1.7 2006-06-24 09:37:49 nemesis Exp $
 *
 * Copyright (C) 2005-2006 Kees Cook
 * kees@outflux.net, http://outflux.net/
 * 
 * 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.
 * http://www.gnu.org/copyleft/gpl.html
 *
 */

/*

   Lots of thanks go to the Wine and MPlayer DLL loading/linking code.  The
   DirectShow loading code is all from MPlayer, with modifications to allow
   SourceFilter and AsyncReader instantiation.

 */

#define INITGUID

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <windows.h>
#include <strmif.h>

#include <uuids.h>


char * version="$Revision: 1.7 $";
int verbose = 0;
int debug = 0;
int windowClicker = 0;
char * dllName = NULL;
char * className = NULL;
char * extension = NULL;
char * outputdir = "."; // by default, write output to the current directory
CLSID classID;

#undef Debug
#ifndef Debug
# define Debug if (debug)
#endif



/**
   User will allocate and fill format structures, call Create(),
   and then set up m_pAll.
 **/

typedef struct _DS_Filter DS_Filter;
struct _DS_Filter
{
    HMODULE m_iHandle;
    IBaseFilter* m_pFilter;
    IFileSourceFilter* m_pFileSource;
    IAsyncReader * m_pAsyncReader;
    IPin* m_pInputPin;
    IPin* m_pOutputPin;

    IBaseFilter* m_pSrcFilter;
    IPin* m_pOurInput;

    AM_MEDIA_TYPE *m_pOurType, *m_pDestType;
    IMemAllocator* m_pAll;
    IMemInputPin* m_pImp;
};

DS_Filter * filter=NULL;

void DS_Filter_Destroy(DS_Filter* This)
{
    if (This->m_pOurInput)
        This->m_pOurInput->lpVtbl->Release(This->m_pOurInput);
    if (This->m_pInputPin)
        This->m_pInputPin->lpVtbl->Disconnect(This->m_pInputPin);
    if (This->m_pOutputPin)
        This->m_pOutputPin->lpVtbl->Disconnect(This->m_pOutputPin);
    if (This->m_pFilter)
        This->m_pFilter->lpVtbl->Release(This->m_pFilter);
    if (This->m_pFileSource)
        This->m_pFileSource->lpVtbl->Release(This->m_pFileSource);
    if (This->m_pAsyncReader)
        This->m_pAsyncReader->lpVtbl->Release(This->m_pAsyncReader);
    if (This->m_pOutputPin)
        This->m_pOutputPin->lpVtbl->Release(This->m_pOutputPin);
    if (This->m_pInputPin)
        This->m_pInputPin->lpVtbl->Release(This->m_pInputPin);
    if (This->m_pImp)
        This->m_pImp->lpVtbl->Release(This->m_pImp);

    if (This->m_pSrcFilter)
        This->m_pSrcFilter->lpVtbl->Release(This->m_pSrcFilter);

    if (This->m_iHandle)
        FreeLibrary(This->m_iHandle);

    free(This);

    // FIXME - we are still leaving few things allocated!
    CoUninitialize();
}

DS_Filter* DS_FilterCreate(const char* dllname, const GUID* id,
                           AM_MEDIA_TYPE* in_fmt,
                           AM_MEDIA_TYPE* out_fmt, WCHAR * filename)
{
    int init = 0;
    const char* em = NULL;
    HRESULT result;
    DS_Filter* This = (DS_Filter*) malloc(sizeof(DS_Filter));
    if (!This)
        return NULL;

    CoInitialize(0L);

    This->m_pFilter = NULL;
    This->m_pFileSource = NULL;
    This->m_pAsyncReader = NULL;
    This->m_pInputPin = NULL;
    This->m_pOutputPin = NULL;
    This->m_pSrcFilter = NULL;
    This->m_pOurInput = NULL;
    This->m_pAll = NULL;
    This->m_pImp = NULL;

    // at least keep the old DLL handle
    if (filter)
        This->m_iHandle = filter->m_iHandle;
    else
        This->m_iHandle = NULL;

    struct IUnknown* object = NULL;
    for (;;)
    {
        FARPROC func;
        struct IClassFactory* factory = NULL;
        IEnumPins* enum_pins = 0;
        IPin* array[256];
        ULONG fetched;
        unsigned int i;

        Debug printf("DS_Filter: loading %s\n",dllname);
        if (!This->m_iHandle)
            This->m_iHandle = LoadLibraryA(dllname);
        if (!This->m_iHandle)
        {
            em = "could not open DirectShow DLL";
            break;
        }
        Debug printf("DS_Filter: loaded %s\n",dllname);
        func = GetProcAddress(This->m_iHandle, "DllGetClassObject");
        if (!func)
        {
            em = "illegal or corrupt DirectShow DLL";
            break;
        }
        Debug printf("DS_Filter: calling IID_IClassFactory in %s\n",dllname);
        result = func(id, &IID_IClassFactory, (void**)&factory);
        //Debug printf("DS_Filter: IID_IClassFactory in %s returned %x 0x%p\n",dllname,(int)result,factory);
        if (result || !factory)
        {
            em = "no such class object";
            break;
        }
        result = factory->lpVtbl->CreateInstance(factory, 0, &IID_IUnknown, (void**)&object);
        factory->lpVtbl->Release(factory);
        if (result || !object)
        {
            em = "class factory failure";
            break;
        }
        result = object->lpVtbl->QueryInterface(object, &IID_IBaseFilter, (void**)&This->m_pFilter);
        //object->lpVtbl->Release((IUnknown*)object);
        if (result || !This->m_pFilter)
        {
            em = "object does not provide IBaseFilter interface";
            break;
        }
        // show VendorInfo
        if (verbose) {
            char name[128];
            WCHAR * pVendorInfo;
            memset(name,0,128);
            result = This->m_pFilter->lpVtbl->QueryVendorInfo(This->m_pFilter, &pVendorInfo);
            if (result == 0) {
                WideCharToMultiByte(0, 0, pVendorInfo, 128, name, 127, 0, 0);
                CoTaskMemFree(pVendorInfo);
                printf("%s VendorInfo: '%s'\n",dllname,name);
            }
        }
        // show FilterInfo
        if (verbose) {
            char name[MAX_FILTER_NAME];
            FILTER_INFO info;
            memset(name,0,MAX_FILTER_NAME);
            result = This->m_pFilter->lpVtbl->QueryFilterInfo(This->m_pFilter, &info);
            if (result == 0) {
                WideCharToMultiByte(0, 0, info.achName, MAX_FILTER_NAME, name, MAX_FILTER_NAME-1, 0, 0);
                printf("%s FilterInfo: '%s'\n",dllname,name);
            }
        }

        // enumerate pins
        result = This->m_pFilter->lpVtbl->EnumPins(This->m_pFilter, &enum_pins);
        if (result || !enum_pins)
        {
            em = "could not enumerate pins";
            break;
        }

        enum_pins->lpVtbl->Reset(enum_pins);
        result = enum_pins->lpVtbl->Next(enum_pins, (ULONG)256, (IPin**)array, &fetched);
        Debug printf("Pins enumeration returned %ld pins, result is %x\n", fetched, (int)result);

        for (i = 0; i < fetched; i++)
        {
            int direction = -1;

            if (verbose) {
                // show PinInfo
                PIN_INFO info;
                char name[128];
                array[i]->lpVtbl->QueryPinInfo(array[i], (PIN_INFO*)&info);

                memset(name,0,128);
                WideCharToMultiByte(0, 0, info.achName, 128, name, 127, 0, 0);
                printf("Pin %d: '%s' (Direction: %s)\n",
                    i, name,
                    info.dir == 0 ? "In" :
                    info.dir == 1 ? "Out" : "Unknown");

                // show MediaTypes
                AM_MEDIA_TYPE* media_array[256];
                ULONG media_types=0;
                IEnumMediaTypes * enum_media = 0;
                int j;
                if (array[i]->lpVtbl->EnumMediaTypes(array[i], &enum_media)==0) {
                    printf("%s Pin '%d' supports the following AM_MEDIA_TYPE:\n",dllname,i);
                    enum_media->lpVtbl->Reset(enum_media);
                    result = enum_media->lpVtbl->Next(enum_media, (ULONG)256, media_array, &media_types);
                    Debug printf("MediaTypes enumeration returned %ld types, error is %x\n", media_types, (int)result);
                    for (j=0;j<media_types;j++) {
                            printf("%d: lSampleSize %lu cbFormat %lu\n"
                                   "\tmajortype: %08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\n"
                                   "\tsubtype: %08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\n"
                                   "\tformattype: %08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\n",
                                    j,
                                    media_array[j]->lSampleSize,
                                    media_array[j]->cbFormat,

                                    (unsigned)media_array[j]->majortype.Data1,
                                    media_array[j]->majortype.Data2,
                                    media_array[j]->majortype.Data2,
                                    media_array[j]->majortype.Data4[0],
                                    media_array[j]->majortype.Data4[1],
                                    media_array[j]->majortype.Data4[2],
                                    media_array[j]->majortype.Data4[3],
                                    media_array[j]->majortype.Data4[4],
                                    media_array[j]->majortype.Data4[5],
                                    media_array[j]->majortype.Data4[6],
                                    media_array[j]->majortype.Data4[7],

                                    (unsigned)media_array[j]->subtype.Data1,
                                    media_array[j]->subtype.Data2,
                                    media_array[j]->subtype.Data2,
                                    media_array[j]->subtype.Data4[0],
                                    media_array[j]->subtype.Data4[1],
                                    media_array[j]->subtype.Data4[2],
                                    media_array[j]->subtype.Data4[3],
                                    media_array[j]->subtype.Data4[4],
                                    media_array[j]->subtype.Data4[5],
                                    media_array[j]->subtype.Data4[6],
                                    media_array[j]->subtype.Data4[7],

                                    (unsigned)media_array[j]->formattype.Data1,
                                    media_array[j]->formattype.Data2,
                                    media_array[j]->formattype.Data2,
                                    media_array[j]->formattype.Data4[0],
                                    media_array[j]->formattype.Data4[1],
                                    media_array[j]->formattype.Data4[2],
                                    media_array[j]->formattype.Data4[3],
                                    media_array[j]->formattype.Data4[4],
                                    media_array[j]->formattype.Data4[5],
                                    media_array[j]->formattype.Data4[6],
                                    media_array[j]->formattype.Data4[7]

                                            );
                    }
                }
            }

            array[i]->lpVtbl->QueryDirection(array[i], (PIN_DIRECTION*)&direction);
            if (!This->m_pInputPin && direction == 0)
            {
                This->m_pInputPin = array[i];
                This->m_pInputPin->lpVtbl->AddRef(This->m_pInputPin);
            }
            if (!This->m_pOutputPin && direction == 1)
            {
                This->m_pOutputPin = array[i];
                This->m_pOutputPin->lpVtbl->AddRef(This->m_pOutputPin);
            }
            array[i]->lpVtbl->Release(array[i]);
        }
        if (!This->m_pOutputPin)
        {
            em = "could not find output pin";
            break;
        }

        This->m_pOurType = in_fmt;
        {
            // look for source file loading interface
            result = object->lpVtbl->QueryInterface(object, &IID_IFileSourceFilter, (void**)&This->m_pFileSource);
            //object->lpVtbl->Release((IUnknown*)object);
            if (result || !This->m_pFileSource)
            {
                em = "could not find input pin and object does not provide IFileSourceFilter interface";
                break;
            }
            //This->m_pFileSource->lpVtbl->AddRef(This->m_pFileSource);
            //
        Debug wprintf(L"Trying to call Load on '%s'...\n",filename);
        result = This->m_pFileSource->lpVtbl->Load(This->m_pFileSource,
                                                     filename,
                                                     This->m_pOurType);
        Debug printf("\tLoad done (%x)\n",(unsigned)result);
        if (result)
        {
            em = "could not Load source file";
            break;
        }


        if (This->m_pAsyncReader) {
            This->m_pAsyncReader->lpVtbl->Release(This->m_pAsyncReader);
            This->m_pAsyncReader=NULL;
        }
        result = This->m_pOutputPin->lpVtbl->QueryInterface(This->m_pOutputPin, &IID_IAsyncReader, (void**)&This->m_pAsyncReader);
        if (result)
        {
            em = "could not find async reader interface";
            break;
        }
        //filter->m_pAsyncReader->lpVtbl->AddRef(filter->m_pAsyncReader);
        }

        if (!This->m_pAsyncReader) {
            if (!out_fmt) {
                em = "need output format";
                break;
            }
            This->m_pDestType = out_fmt;
        }

        init++;
        break;
    }

    if (object)
        object->lpVtbl->Release((IUnknown*)object);

    if (!init)
    {
        
        Debug printf("DS_Filter: aborting %s (%s)\n",dllname,em);
        DS_Filter_Destroy(This);
        printf("Warning: DS_Filter() %s.\n  (DLL=%s, r=0x%x)\n", em, dllname, (unsigned)result);
        This = 0;
    }
    return This;
}

BOOL
DS_FilterLoad(DS_Filter * filter, WCHAR * filename)
{
    BOOL loaded=FALSE;
    BOOL result;
    const char* em = NULL;

    if (!filter) {
        printf("Whoa, NULL filter\n");
        return FALSE;
    }
    printf("%p\n",filter);
    if (!filter->m_pFileSource) {
        printf("Whoa, NULL FileSource\n");
        return FALSE;
    }
    printf("%p\n",filter->m_pFileSource);
    if (!filter->m_pOurType) {
        printf("Whoa, NULL OurType\n");
        return FALSE;
    }
    printf("%p\n",filter->m_pOurType);
    if (!filter->m_pOutputPin) {
        printf("Whoa, NULL OutputPin\n");
        return FALSE;
    }
    printf("%p\n",filter->m_pOutputPin);

    for (;;) {
        Debug wprintf(L"Trying to call Load on '%s'...\n",filename);
        result = filter->m_pFileSource->lpVtbl->Load(filter->m_pFileSource,
                                                     filename,
                                                     filter->m_pOurType);
        Debug printf("\tLoad done (%x)\n",(unsigned)result);
        if (result)
        {
            em = "could not Load source file";
            break;
        }


        if (filter->m_pAsyncReader) {
            filter->m_pAsyncReader->lpVtbl->Release(filter->m_pAsyncReader);
            filter->m_pAsyncReader=NULL;
        }
        result = filter->m_pOutputPin->lpVtbl->QueryInterface(filter->m_pOutputPin, &IID_IAsyncReader, (void**)&filter->m_pAsyncReader);
        if (result)
        {
            em = "could not find async reader interface";
            break;
        }
        //filter->m_pAsyncReader->lpVtbl->AddRef(filter->m_pAsyncReader);

        loaded=TRUE;
        break;
    }

    if (!loaded) {
        Debug wprintf(L"%S: aborting '%s' (%S)\n",__FUNCTION__,filename,em);
    }

    return loaded;
}

BOOL
DS_FilterDump(DS_Filter * filter, char * filename)
{
    LONGLONG total, avail, pos;
    HRESULT hr;
    FILE * fp;

    if (!filter) {
        printf("Whoa, NULL filter\n");
        return FALSE;
    }
    if (!filter->m_pAsyncReader) {
        printf("Whoa, NULL AsyncReader\n");
        return FALSE;
    }

    if (!(fp=fopen(filename,"wb"))) {
        perror(filename);
        return FALSE;
    }

    hr = filter->m_pAsyncReader->lpVtbl->Length(filter->m_pAsyncReader,
                                                &total, &avail);
    if (hr) {
        printf("Length failed (%x)\n",(unsigned int)hr);
        return FALSE;
    }
    Debug printf("%llu total ",total);
    Debug printf("%llu avail\n",avail);

    /* Using Request call sequences...  (from Wine's filtergraph code)
     
    ALLOCATOR_PROPERTIES alloc_prop;
    IMemAllocator * alloc=NULL;

    alloc_prop.cBuffers=3;
    alloc_prop.cbBuffer=64 * 1024;
    alloc_prop.cbAlign=1;
    alloc_prop.cbPrefix=0;
    hr = filter->m_pAsyncReader->lpVtbl->RequestAllocator(filter->m_pAsyncReader,
                                                NULL,
                                                &alloc_prop, &alloc);
    if (hr) {
        printf("RequestAllocator failed (%x)\n",(unsigned int)hr);
        return FALSE;
    }

    // allow memory allocation
    hr = alloc->lpVtbl->Commit(alloc);
    if (hr) {
        printf("Commit failed (%x)\n",(unsigned int)hr);
        return FALSE;
    }

    // find props
    hr = alloc->lpVtbl->GetProperties(alloc,&alloc_prop);
    if (hr) {
        printf("GetProperties failed (%x)\n",(unsigned int)hr);
        return FALSE;
    }
    printf("Allocator Properties:\n");
    printf("\tcBuffers: %lu\n",alloc_prop.cBuffers);
    printf("\tcbBuffer: %lu\n",alloc_prop.cbBuffer);
    printf("\tcbAlign:  %lu\n",alloc_prop.cbAlign);
    printf("\tcbPrefix: %lu\n",alloc_prop.cbPrefix);

    pos=0;
    avail=total;

#define MEDIATIME_FROM_BYTES(x) ((LONGLONG)(x) * 10000000)
#define SEC_FROM_MEDIATIME(time) ((time) / 10000000)
#define BYTES_FROM_MEDIATIME(time) SEC_FROM_MEDIATIME(time)
#define ALIGNDOWN(value,boundary) ((value) & ~((boundary)-1))
#define ALIGNUP(value,boundary) (ALIGNDOWN((value)-1, (boundary)) + (boundary))

    REFERENCE_TIME rtStart, rtStop, rtCurrent;
    rtStart = 0;
    rtStop = ((LONGLONG)0x7fffffff << 32) | 0xffffffff;
    
    rtCurrent = MEDIATIME_FROM_BYTES(ALIGNDOWN(BYTES_FROM_MEDIATIME(rtStart), alloc_prop.cbAlign));

    while (rtCurrent < rtStop) {
        REFERENCE_TIME rtSampleStop;
        IMediaSample * sample=NULL;
        DWORD id=0;
        BYTE * buffer=NULL;
        size_t size=0;

        // get buffer
        hr = alloc->lpVtbl->GetBuffer(alloc,&sample,NULL,NULL,0);
        if (hr) {
            printf("GetBuffer failed (%x)\n",(unsigned int)hr);
            return FALSE;
        }

        rtSampleStop = rtCurrent + MEDIATIME_FROM_BYTES(sample->lpVtbl->GetSize(sample));
        if (rtSampleStop > rtStop)
            rtSampleStop = MEDIATIME_FROM_BYTES(ALIGNUP(BYTES_FROM_MEDIATIME(rtStop), alloc_prop.cbAlign));

        // set the time
        //hr = sample->lpVtbl->SetMediaTime(sample,&start,&stop);
        hr = sample->lpVtbl->SetTime(sample,&rtCurrent,&rtSampleStop);
        if (hr) {
            printf("SetMediaTime failed (%x)\n",(unsigned int)hr);
            return FALSE;
        }
        rtCurrent = rtSampleStop;

        // issue request
        hr = filter->m_pAsyncReader->lpVtbl->Request(filter->m_pAsyncReader,
                                                     sample, id);
        if (hr) {
            printf("Request failed (%x)\n",(unsigned int)hr);
            return FALSE;
        }
        hr = filter->m_pAsyncReader->lpVtbl->WaitForNext(filter->m_pAsyncReader,
                                                         10000,
                                                         &sample, &id);
        if (hr) {
            printf("WaitForNext failed (%x)\n",(unsigned int)hr);
            return FALSE;
        }

        hr = sample->lpVtbl->GetPointer(sample,&buffer);
        if (hr) {
            printf("GetPointer failed (%x)\n",(unsigned int)hr);
            return FALSE;
        }

        // calculate size
        size = (size_t)sample->lpVtbl->GetActualDataLength(sample);

        if (fwrite(buffer+alloc_prop.cbPrefix,size,1,fp)!=1) {
            perror(filename);
            fclose(fp);
            return FALSE;
        }

        if (sample) sample->lpVtbl->Release(sample);
        sample=NULL;

        avail-=size;
        pos+=size;

        {
            double percent;
            if (!total) percent=0.0;
            else        percent=(double)pos*100.0/(double)total;

            printf("%llu/",pos);
            printf("%llu ",total);
            printf("%.2f%% (%u)\r",percent,size);
            fflush(NULL);
        }
    }

    fclose(fp);
    return TRUE;
    */


    /* Using direct SyncRead calls... */
    pos=0;
    avail=total;
#define DUMP_SIZE (1024*1024)
    BYTE bigbuf[DUMP_SIZE];
    while (avail) {
        LONG size=DUMP_SIZE;
        if (avail<size) size=avail;
        hr = filter->m_pAsyncReader->lpVtbl->SyncRead(filter->m_pAsyncReader,
                                                      pos, size, bigbuf);
        if (hr) {
            printf("SyncRead failed (%x) at %llu wanting %lu\n",(unsigned int)hr,pos,size);
            return FALSE;
        }
        avail-=size;
        pos+=size;

        if (fwrite(bigbuf,size,1,fp)!=1) {
            perror(filename);
            fclose(fp);
            return FALSE;
        }

        {
            double percent;
            if (!total) percent=0.0;
            else        percent=(double)pos*100.0/(double)total;

            printf("\t%llu/",pos);
            printf("%llu ",total);
            printf("%.2f%%\r",percent);
            fflush(NULL);
        }
    }

    fclose(fp);
    return TRUE;
}



BOOL
filter_init(WCHAR * filename)
{
    AM_MEDIA_TYPE input_media;
    AM_MEDIA_TYPE output_media;

    memset(&input_media, 0, sizeof(input_media));
    input_media.majortype = MEDIATYPE_Stream;
    input_media.subtype = MEDIASUBTYPE_None;
    input_media.formattype = FORMAT_None;
    input_media.bFixedSizeSamples = 0;
    input_media.bTemporalCompression = 1;
    input_media.lSampleSize = 0;
    input_media.pUnk = 0;
    input_media.cbFormat = 0;
    input_media.pbFormat = 0;

    output_media = input_media;

    /*
    memset(&output_media, 0, sizeof(output_media));
    output_media.majortype = MEDIATYPE_Stream;
    output_media.subtype = MEDIASUBTYPE_None;
    output_media.formattype = FORMAT_None;
    output_media.bFixedSizeSamples = 0;
    output_media.bTemporalCompression = 1;
    output_media.lSampleSize = 0;
    output_media.pUnk = 0;
    output_media.cbFormat = 0;
    output_media.pbFormat = 0;
    */

    filter = DS_FilterCreate(dllName, &classID,
                             &input_media, &output_media, filename);

    return (filter!=NULL);
}

LONG getSourceFilterClassID(char * extension,char *szClass,PDWORD pdwSize)
{
    char * szBase = "Software\\Classes\\Media Type\\Extensions\\";
    char * szSubkey = NULL;
    DWORD dwType;
    HKEY hKey;

    szSubkey = (char*)malloc(strlen(extension)+strlen(szBase)+1);
    sprintf(szSubkey,"%s%s",szBase,extension);

    if (RegOpenKeyA(HKEY_LOCAL_MACHINE,szSubkey,&hKey) != ERROR_SUCCESS) {
        fprintf(stderr,"Cannot read '%s' registry key\n",szSubkey);
        free(szSubkey);
        return 1;
    }
    free(szSubkey);

    if (RegQueryValueExA(hKey,
                "Source Filter",
                NULL,
                &dwType,
                szClass,
                pdwSize) != ERROR_SUCCESS) {
        fprintf(stderr,"Could not load 'Source Filter' for '%s'!\n",extension);
        RegCloseKey(hKey);
        return 1;
    }

    RegCloseKey(hKey);

    return 0;
}

char * allocateValue(HKEY hKey, char *szValueName)
{
    char * szValue = NULL;
    DWORD dwValueSize = 0;

    if (RegQueryValueExA(hKey,
                szValueName,
                NULL,
                NULL,
                szValue,
                &dwValueSize) != ERROR_SUCCESS) {
        fprintf(stderr,"Cannot read size of registry value for '%s'!\n",
                szValueName ? szValueName : "default");
        return NULL;
    }

    if (!(szValue = malloc(dwValueSize))) {
        perror("malloc");
        return NULL;
    }

    if (RegQueryValueExA(hKey,
                szValueName,
                NULL,
                NULL,
                szValue,
                &dwValueSize) != ERROR_SUCCESS) {
        fprintf(stderr,"Cannot read size of registry value for '%s'!\n",
                szValueName ? szValueName : "default");
        return NULL;
    }

    return szValue;
}

char * getDllFromClassID(WCHAR *wszClass)
{
    char * szName = NULL;
    char * szDllPath = NULL;
    char * szBase = "Software\\Classes\\CLSID";
    HKEY hKey;
    HKEY hKeyID;
    HKEY hKeyServer;

    if (RegOpenKeyA(HKEY_LOCAL_MACHINE,szBase,&hKey) != ERROR_SUCCESS) {
        fprintf(stderr,"Cannot read '%s' registry key\n",szBase);
        return NULL;
    }
    if (RegOpenKeyW(hKey,wszClass,&hKeyID) != ERROR_SUCCESS) {
        fwprintf(stderr,L"Cannot read CLSID '%s' registry key\n",wszClass);
        RegCloseKey(hKey);
        return NULL;
    }

    // check that we can find a DLL
    if (!(szName = allocateValue(hKeyID, NULL))) {
        fwprintf(stderr,L"Cannot read CLSID registry key for '%s'!\n",wszClass);
        RegCloseKey(hKeyID);
        RegCloseKey(hKey);
        return NULL;
    }
    if (verbose) {
        printf("Using SourceFilter '%s' ...\n",szName);
    }
    free(szName);

    if (RegOpenKeyA(hKeyID,"InprocServer32",&hKeyServer) != ERROR_SUCCESS) {
        fwprintf(stderr,L"Cannot open InprocServer32 registry key for 'CLSID\\%s'!\n",
                wszClass);
        RegCloseKey(hKeyID);
        RegCloseKey(hKey);
        return NULL;
    }
    if (!(szDllPath = allocateValue(hKeyServer, NULL))) {
        fwprintf(stderr,L"Cannot read InprocServer32 registry key for 'CLSID\\%s'!\n",
                wszClass);
        RegCloseKey(hKeyServer);
        RegCloseKey(hKeyID);
        RegCloseKey(hKey);
        return NULL;
    }

    RegCloseKey(hKeyServer);
    RegCloseKey(hKeyID);
    RegCloseKey(hKey);

    if (verbose) {
        printf("Using DLL '%s' ...\n",szDllPath);
    }

    return szDllPath;
}

void loadClassID(char * classString)
{
    GUID CLSID_Example={0x01234567, 0x0123, 0x0123,
        { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}};
    int length;
    WCHAR * wclassString;

    length=strlen(classString)+1;
    if (!(wclassString=LocalAlloc(LPTR,sizeof(WCHAR)*length))) {
        perror("malloc");
        exit(1);
    }
    MultiByteToWideChar(CP_ACP, 0, classString, -1, wclassString, length);
    HRESULT hr;
    hr = CLSIDFromString(wclassString,&classID);
    if (hr!=NOERROR) {
        fwprintf(stderr,L"Invalid ClassID:  %s (hr: %x)\n",wclassString,hr);

        WCHAR * example;
        StringFromCLSID(&CLSID_Example,&example);
        fwprintf(stderr,L"Expecting format: %s\n",example);
        exit(1);
    }
    if (verbose) {
        printf("Using ClassID '%s' ...\n",classString);
    }
}

BOOL
run_filter(char * filename)
{
    BOOL result=FALSE;
    char * output, * ptr;
    int length;
    WCHAR * wfilename;

//    if (!filter) return FALSE;

    // Create source filename
    length=strlen(filename)+1; // include NULL in length count
    if (!(wfilename=LocalAlloc(LPTR,sizeof(WCHAR)*length))) {
        perror("malloc");
        return FALSE;
    }
    MultiByteToWideChar(CP_ACP, 0, filename, -1, wfilename, length);

    // find source extension for class ID, if not already specified
    if (!className) {
        char * srcext = NULL;
        char * ext = NULL;
        char szClass[40];
        DWORD dwSize=40;

        if ( (srcext=strrchr(filename,'.')) == NULL) {
            fprintf(stderr,"No -c given, and no file extension found!\n");
            exit(1);
        }
        // lower-case the extension for registry matches
        ext=strdup(srcext);
        for (srcext = ext; srcext && *srcext; srcext++) {
            *srcext=tolower(*srcext);
        }
        if (getSourceFilterClassID(ext,szClass,&dwSize)!=0) {
            fprintf(stderr,"No -c given, and no Class ID in registry!\n");
            exit(1);
        }
        free(ext);
        loadClassID(szClass);
    }

    if (!dllName) {
        WCHAR * wszClass;
        StringFromCLSID(&classID,&wszClass);

        if (!(dllName = getDllFromClassID(wszClass))) {
            fprintf(stderr,"No -f given, and no DLL for Class ID in registry!\n");
            exit(1);
        }
    }

    // Create destination filename
    int extlen = strlen(extension)+1; // include "." in length count
    int dirlen = strlen(outputdir)+1; // include possible "/" in path

    if (!(output=calloc(1,length+extlen+dirlen))) {
        perror("malloc");
        free(wfilename);
        return FALSE;
    }
    sprintf(output,"%s/%s",outputdir,filename);

    // append new extension
    ptr=strrchr(output,'.');
    if (ptr) {
        sprintf(ptr,".%s",extension);
    }
    else {
        strcat(output,".");
        strcat(output,extension);
    }

    // load and dump filter
    if (filter_init(wfilename)) { 
        result = DS_FilterDump(filter,output);
    }

    free(output);
    LocalFree(wfilename);

    return result;
}

BOOL CALLBACK ClickProcWindow(HWND hwnd, LPARAM lParam)
{
    if (hwnd != (HWND)lParam) {
        //printf("I see 0x%x\n",(unsigned int)hwnd);

        //SendMessage(hwnd,WM_KEYDOWN,VK_RETURN,0);
        //SendMessage(hwnd,WM_KEYUP,VK_RETURN,0);
        SendMessage(hwnd,WM_COMMAND,IDOK,0);
    }
    return TRUE;
}

BOOL clickerRunning=FALSE;
DWORD WINAPI ClickProc( LPVOID lpParam )
{
    HWND topWindow = GetTopWindow( NULL );
    clickerRunning=TRUE;
    while (clickerRunning)
    {
        //printf("ping\n");
        EnumWindows(ClickProcWindow,(LPARAM)topWindow);
        Sleep(250);
    }
    return 0;
}

void usage(char * exe)
{
    printf("Usage:\n");
    printf(" %s [OPTIONS] filename ...\n",exe);
    printf("\n");
    printf("  -c {HEX}         Override SourceFilter ClassID for file extension\n");
    printf("  -f SRCFILTER.DLL Override DLL containing SourceFilter ClassID\n");
    printf("  -x EXTENSION     Extension to replace on output filename (default: 'test')\n");
    printf("  -o DIRECTORY     Where to write output files\n");
    printf("  -v               Toggle verbose mode\n");
    printf("  -d               Toggle debugging output\n");
    printf("  -w               Toggle window clicker (does not always work)\n");
    printf("  -h               This help\n");
    exit(1);
}

int main(int argc,char* argv[])
{
    char c;
    int i, result=0;
    DWORD dwThreadId;
    HANDLE hThread;

    printf("srcfilter %s\n",version);
    printf("http://outflux.net/software/pkgs/srcfilter/\n");
    printf("Copyright 2005-2006, Kees Cook <kees@outflux.net>\n");
    printf("This program is licensed under the GNU General Public License\n");
    printf("http://www.gnu.org/copyleft/gpl.html\n");
    printf("\n");

    while( (c = getopt(argc,argv,"hvdwf:c:x:o:")) != -1) {
        switch (c) {
            case ':':
                fprintf(stderr,"Option requires an argument!\n");
                usage(argv[0]);
                break;
            case 'h':
                usage(argv[0]);
                break;
            case 'w':
                windowClicker = !windowClicker;
                break;
            case 'f':
                dllName = strdup(optarg);
                break;
            case 'c':
                className = strdup(optarg);
                break;
            case 'x':
                extension = strdup(optarg);
                break;
            case 'o':
                outputdir = strdup(optarg);
                break;
            case 'd':
                debug=!debug;
                break;
            case 'v':
                verbose=!verbose;
                break;
            case '?':
                fprintf(stderr,"Unknown option!\n");
                usage(argv[0]);
                break;
            default:
                fprintf(stderr,"Unknown option '%c'!\n",c);
                usage(argv[0]);
                break;
        }
    }

    if (!extension) {
        extension = strdup("test");
    }
    /*
    if (!dllName) {
        fprintf(stderr,"DLL name required!\n");
        usage(argv[0]);
    }
    */
    if (className) loadClassID(className);

    if (windowClicker)
    {
        hThread = CreateThread(
                NULL,
                0,
                ClickProc,
                NULL,
                0,
                &dwThreadId);
    }

    if (optind==argc) {
        fprintf(stderr,"No files to load?!\n");
        usage(argv[0]);
    }

    for (i=optind;i<argc;i++) {
//        if (access(argv[i],F_OK)==0) {
            printf("Loading '%s' ...\n",argv[i]);
            if (run_filter(argv[i])) {
                printf(" OK\n");
            }
            else {
                result++;
                printf(" Failed\n");
            }
            /*
        }
        else {
            result++;
            fprintf(stderr,"access(%s) ",argv[i]);
            perror("failed");
        }
        */
    }

    if (windowClicker)
    {
        clickerRunning=FALSE;
        WaitForSingleObject(hThread,INFINITE);
        CloseHandle(hThread);
    }

    return result;
}

/* vi:set ai ts=4 sw=4 expandtab: */
