/*
 *  SFX to archive converter
 *
 *  Copyright (c) 2008, Veacheslav Patkov.
 *  aLL rigths reserved.
 *
 *  Licensed under GNU GPL v2
 *
 */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

#include "../../../pdk/include/plugin.h"
#include "rsrc.h"

#define SFX2AR_VERSION "0.01"

#define BUFSIZE 100*1024
#define MAXSIGN 14
#define MAXSECTIONS 15
#define ALIGNUP(x,y) ((x+y-1)&(~(y-1)))

typedef struct {
    char *name;
    char *ext;
    unsigned char *sign;
    unsigned char len;
    unsigned char offset;
} signature;

HINSTANCE hinst;
HWND hDLG;
plug_interface *pif;
char buf[BUFSIZE], oname[MAX_PATH];

#define START_OVERLAY_SIGNS 8

signature signs[] = {
    {"unknown type",".ovr",0,0,0},
    {"ZIP archive",".zip","\x50\x4b\x03\x04",4,0},
    {"ZIP archive",".zip","\x50\x4b\x30\x30\x50\x4b",6,0},
    {"RAR archive",".rar","\x52\x61\x72\x21",4,0},
    {"CAB arhcive",".cab","\x4d\x53\x43\x46",4,0},
    {"UHARC arhcive",".uha","\x55\x48\x41",3,0},
    {"7-ZIP arhcive",".7z","\x37\x7a\xbc\xaf\x27\x1c",6,0},
    {"ACE archive",".ace","\x2a\x2a\x41\x43\x45\x2a\x2a",7,7},
    {"NSIS install data",".dat","\x00\x00\x00\x00",4,0},
    {"NSIS uninstall data",".dat","\x01\x00\x00\x00",4,0},
    {"Install Shield data",".dat","InstallShield",13,0},
    {"Inno Setup data (zlib)",".dat","zlb\x1a",4,0},
    {0,0,0,0,0}
};

void addinfo(char *s)
{
    static char buftmp[2048];

    GetDlgItemText(hDLG,IDC_INFO,buftmp,1024);
    strcat(buftmp,s);
    SetDlgItemText(hDLG,IDC_INFO,buftmp);
    SendDlgItemMessage(hDLG,IDC_INFO,WM_VSCROLL,SB_BOTTOM,0);
}

void save_archive(char *iname, char *oname, unsigned int start)
{
    FILE *fi,*fo;
    unsigned int i,k,n;
    unsigned char *p;

    if (!(fi = fopen(iname,"rb"))) {
        addinfo("Error: cannot open input file !\r\n");
        return;
    }
    if (start > 0)
        if (fseek(fi,start,SEEK_SET)) {
            sprintf(buf,"Error: cannot seek input file to offset 0x%08X !\r\n",start);
            addinfo(buf);
            goto close_fi;
        }

    while ((n = fread(buf,1,BUFSIZE,fi)) > 0)
        for (i = MAXSIGN, p = buf; i < n; i++, p++)
            for (k = 1; k < START_OVERLAY_SIGNS; k++)
                if (!memcmp(p+signs[k].offset,signs[k].sign,signs[k].len))
                    goto found;

    addinfo("No archives found...\r\n");
    goto close_fi;
  found:
    sprintf(buf,"Found %s at offset 0x%08X\r\n",signs[k].name,
            (unsigned int)ftell(fi)+i-n-MAXSIGN);
    addinfo(buf);

    if (!oname[0]) {
        strcpy(oname,iname);
        strcat(oname,signs[k].ext);
    }
    SetDlgItemText(hDLG,IDC_FNAME,oname);

    if (!(fo = fopen(oname,"wb"))) {
        addinfo("Error: cannot open output file !\r\n");
        goto close_fi;
    }

    fseek(fi,i-n-MAXSIGN,SEEK_CUR);
    while ((n = fread(buf,1,BUFSIZE,fi)) > 0)
        if (fwrite(buf,1,n,fo) != n) {
            addinfo("Error: cannot write output file !\r\n");
            goto close_fo;
        }

    addinfo("Done.\r\n");

  close_fo:
    fclose(fo);
  close_fi:
    fclose(fi);
}

int get_overlay_type(char *buf)
{
    int i;
    for (i = 1; signs[i].name; i++)
        if (!memcmp(buf+signs[i].offset,signs[i].sign,signs[i].len))
            return i;
    return 0;
}

void save_overlay(char *iname, char *oname)
{
    FILE *fi,*fo;
    unsigned int n,type = 0,align,fsize,pesize;
    static IMAGE_DOS_HEADER dos_header;
    static IMAGE_NT_HEADERS nt_header;
    static IMAGE_SECTION_HEADER sections[MAXSECTIONS];

    if (!(fi = fopen(iname,"rb"))) {
        addinfo("Error: cannot open input file !\r\n");
        return;
    }

    fseek(fi,0,SEEK_END);
    fsize = ftell(fi);

    fseek(fi,0,SEEK_SET);
    if (fsize < sizeof(IMAGE_DOS_HEADER)) {
        addinfo("Error: input file is not Win32 PE !\r\n");
        goto close_fi;
    }
    fread(&dos_header,1,sizeof(IMAGE_DOS_HEADER),fi);
    if (dos_header.e_magic != IMAGE_DOS_SIGNATURE ||
        dos_header.e_lfanew > (fsize-sizeof(IMAGE_NT_HEADERS))) {
        addinfo("Error: input file is not Win32 PE !\r\n");
        goto close_fi;
    }

    fseek(fi,dos_header.e_lfanew,SEEK_SET);
    fread(&nt_header,1,sizeof(IMAGE_NT_HEADERS),fi);
    n = nt_header.FileHeader.NumberOfSections;
    if (nt_header.Signature != IMAGE_NT_SIGNATURE || n > MAXSECTIONS ||
        fread(&sections,sizeof(IMAGE_SECTION_HEADER)*n,1,fi) != 1) {
        addinfo("Error: input file is not Win32 PE !\r\n");
        goto close_fi;
    }
    align = nt_header.OptionalHeader.FileAlignment;
    pesize = sections[n-1].SizeOfRawData + sections[n-1].PointerToRawData;
    pesize = ALIGNUP(pesize,align);
    if (pesize >= fsize) {
        addinfo("PE overlay is not found...\r\n");
        goto close_fi;
    }

    fseek(fi,pesize,SEEK_SET);
    if (fread(buf,MAXSIGN,1,fi) == 1)
        type = get_overlay_type(buf);

    sprintf(buf,"PE overlay found at offset 0x%08X (%s)\r\n",pesize,signs[type].name);
    addinfo(buf);

    if (!oname[0]) {
        strcpy(oname,iname);
        strcat(oname,signs[type].ext);
    }
    SetDlgItemText(hDLG,IDC_FNAME,oname);

    if (!(fo = fopen(oname,"wb"))) {
        addinfo("Error: cannot open output file !\r\n");
        goto close_fi;
    }

    fseek(fi,pesize,SEEK_SET);
    while ((n = fread(buf,1,BUFSIZE,fi)) > 0)
        if (fwrite(buf,1,n,fo) != n) {
            addinfo("Error: cannot write output file !\r\n");
            goto close_fo;
        }

    addinfo("Done.\r\n");

  close_fo:
    fclose(fo);
  close_fi:
    fclose(fi);
}

void sfx2archive(void)
{
    int i, c;
    char offset[9] = "0", *iname = pif->fname;

    if (IsDlgButtonChecked(hDLG,IDC_SEARCH) == BST_CHECKED) {
        if (!GetDlgItemText(hDLG,IDC_OFFSET,offset,9))
            SetDlgItemText(hDLG,IDC_OFFSET,"00000000");
        for (i = 0; i < 8 && (c = tolower(offset[i])) != 0; i++)
            if (!((c >= '0' && c <= '9') ||
                  (c >= 'a' && c <= 'f'))) {
                sprintf(buf,"Error: incorrect hexademical number `%s' !\r\n",offset);
                addinfo(buf);
                return;
            }
        save_archive(iname,oname,strtoul(offset,0,16));
    } else
        save_overlay(iname,oname);
}

BOOL dialog_main(HWND hdlg, UINT umsg, WPARAM wparam, LPARAM lparam)
{
    static HFONT hfont;
    static OPENFILENAME ofn;

    switch (umsg)
    {
        case WM_INITDIALOG:
            hDLG = hdlg;
            SendMessage(hdlg,WM_SETTEXT,0,(LPARAM)" [ SFX to archive converter ]");
            CheckRadioButton(hdlg,IDC_SEARCH,IDC_OVERLAY,IDC_SEARCH);
            hfont = CreateFont(10,0,0,0,FW_NORMAL,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
                               CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,"Terminal");
            SendDlgItemMessage(hdlg,IDC_INFO,WM_SETFONT,(WPARAM)hfont,(LPARAM)0);
            break;

        case WM_COMMAND:
            switch (LOWORD(wparam)) {
                case IDC_BROWSE:
                    ofn.lStructSize = sizeof(OPENFILENAME);
                    ofn.hwndOwner = hdlg;
                    ofn.lpstrFile = oname;
                    ofn.nMaxFile = MAX_PATH;
                    ofn.lpstrInitialDir = ".";
                    ofn.Flags = OFN_PATHMUSTEXIST | OFN_LONGNAMES | OFN_HIDEREADONLY;
                    ofn.lpstrTitle = " [ SFX to archive converter ] - set output file...";
                    if (GetSaveFileName(&ofn))
                        SetDlgItemText(hdlg,IDC_FNAME,oname);
                    break;
                case IDC_CONVERT:
                    sfx2archive();
                    break;
                case IDC_ABOUT:
                    addinfo("SFX to archive converter version " SFX2AR_VERSION "\r\n"
                            "Copyright (C) 2008 Veacheslav Patkov\r\n"
                            "All rights reserved.\r\n");
                    break;
                case IDC_CLOSE:
                    goto end_dialog;
                case IDC_SEARCH:
                case IDC_OVERLAY: {
                    int b = IsDlgButtonChecked(hDLG,IDC_SEARCH) == BST_CHECKED ? 1 : 0;
                    EnableWindow(GetDlgItem(hdlg,IDC_OFFSET),b);
                }
                break;
            }
            break;

        case WM_CLOSE:
          end_dialog:
            if (hfont)
                DeleteObject(hfont);
            EndDialog(hdlg,0);
            break;
    }
    return 0;
}

char *pname(void)
{
    return "SFX to archive converter";
}

void pmain(plug_interface *pi)
{
    if (!*pi->fname) {
        MessageBox(0,"File name is not selected !","Error !",MB_ICONERROR);
        return;
    }
    pif = pi;
    DialogBoxParam(hinst,MAKEINTRESOURCE(IDD_DIALOG),pi->hwnd,(DLGPROC)&dialog_main,0);
    return;
}

int __stdcall dllentry(HINSTANCE hinstance, DWORD reason, LPVOID reserved)
{
    hinst = hinstance;
    return 1;
}
