//	Album List for Winamp
//	Copyright (C) 1999-2001 Safai Ma
//
//	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., 675 Mass Ave, Cambridge, MA 02139, USA.
#include "stdafx.h"
#include <stdlib.h>
#include <prsht.h>
#include <urlmon.h>
#include <time.h>
#include <regstr.h>
#include <mbstring.h>
#include "config.h"
#include "gen.h"
#include "resource.h"
#include "frontend.h"
#include "wa_ipc.h"
#include "winampalbumlist.h"
#include "WatchConnection.h"
#include "BindStatusCallback.h"
#include "Pages.h"
#include "Property.h"
#include "APIHijack.h"

#define IDC_WA_CURSORS                  1007
#define IDC_SNAP                        1108
#define IDC_HIGHLIGHT                   1110
#define IDC_ADDFILES                    1138
#define IDC_RANDOM                      1150
#define IDC_CHDIR                       1160
#define IDC_PLNUMS                      1168
#define IDC_CONVERT1                    1172
#define IDC_CONVERT2                    1173
#define IDC_PLFONTSIZE                  1177
#define IDC_AOVD                        1179
#define IDC_SNAPW                       1180
#define WINAMP_FILE_QUIT                40001
#define WINAMP_OPTIONS_AOT              40019
#define WINAMP_NEXT_WINDOW              40063
#define WINAMP_OPTIONS_EASYMOVE         40186
#define WINAMP_REFRESHSKIN              40291

#define ALBUMLIST_SUBMENU               50022
#define ALBUMLIST_SHOW                  50023
#define ALBUMLIST_HIDE                  50024

#define TIME_BETWEEN_VERSION_CHECK		86400	// 10 days in seconds

//
// The supported WM_APPCOMMANDS events
//
#ifndef WM_APPCOMMAND
#define WM_APPCOMMAND					0x319
#define APPCOMMAND_MEDIA_NEXTTRACK		11
#define APPCOMMAND_MEDIA_PREVIOUSTRACK	12
#define APPCOMMAND_MEDIA_STOP			13
#define APPCOMMAND_MEDIA_PLAY_PAUSE		14
#define FAPPCOMMAND_MASK				0X8000
#define GET_APPCOMMAND_LPARAM(lParam)	((short) (HIWORD (lParam) & ~FAPPCOMMAND_MASK))
#endif

HMODULE hMod = NULL;

CWinampAlbumList winampAL;

prefsDlgRec ALprefsDlgRec;

// Function pointer types.
typedef BOOL (WINAPI *TextOutA_Type)(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);
typedef BOOL (WINAPI *ExtTextOutA_Type)(HDC hdc, int X, int Y, UINT fuOptions, CONST RECT *lprc, LPCTSTR lpString, UINT cbCount, CONST INT *lpDx);
typedef BOOL (WINAPI *PolyTextOutA_Type)(HDC hdc, CONST POLYTEXT *pptxt, int cStrings);
 
typedef int (WINAPI *DrawTextA_Type)(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);
typedef int (WINAPI *DrawTextExA_Type)(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat, LPDRAWTEXTPARAMS lpDTParams);

// Function prototypes.
BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);
BOOL WINAPI MyExtTextOutA(HDC hdc, int X, int Y, UINT fuOptions, CONST RECT *lprc, LPCTSTR lpString, UINT cbCount, CONST INT *lpDx);
BOOL WINAPI MyPolyTextOutA(HDC hdc, CONST POLYTEXT *pptxt, int cStrings);

int WINAPI MyDrawTextA(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);
int WINAPI MyDrawTextExA(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat, LPDRAWTEXTPARAMS lpDTParams);

// Hook structure.
enum
{
    GDIFN_TextOutA = 0,
    GDIFN_ExtTextOutA = 1,
	GDIFN_PolyTextOutA = 2
};

enum
{
    UserFN_DrawTextA = 0,
    UserFN_DrawTextExA = 1
};

SDLLHook GDIHook = 
{
    "GDI32.DLL",
    false, NULL,		// Default hook disabled, NULL function pointer.
    {
        { "TextOutA", MyTextOutA },
        { "ExtTextOutA", MyExtTextOutA },
		{ "PolyTextOutA", MyPolyTextOutA },
        { NULL, NULL }
    }
};

SDLLHook UserHook = 
{
    "USER32.DLL",
    false, NULL,		// Default hook disabled, NULL function pointer.
    {
		{ "DrawTextA", MyDrawTextA },
//		{ "DrawTextExA", MyDrawTextExA },
        { NULL, NULL }
    }
};

// Hook function.
BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString)
{
    // Let the world know we're working.
    MessageBeep( MB_ICONINFORMATION );

    OutputDebugString( "TESTDLL: MyTextOutA called.\n" );

    TextOutA_Type OldFn = 
        (TextOutA_Type)GDIHook.Functions[GDIFN_TextOutA].OrigFn;
    return OldFn(hdc, 0, 0, lpString, cbString);
}

BOOL WINAPI MyExtTextOutA(HDC hdc, int X, int Y, UINT fuOptions, CONST RECT *lprc, LPCTSTR lpString, UINT cbCount, CONST INT *lpDx)
{
    // Let the world know we're working.
    MessageBeep( MB_ICONINFORMATION );

    OutputDebugString( "TESTDLL: MyExtTextOutA called.\n" );

    ExtTextOutA_Type OldFn = 
        (ExtTextOutA_Type)GDIHook.Functions[GDIFN_ExtTextOutA].OrigFn;
    return OldFn(hdc, X, Y, fuOptions, lprc, lpString, cbCount, lpDx);
}

BOOL WINAPI MyPolyTextOutA(HDC hdc, CONST POLYTEXT *pptxt, int cStrings)
{
    // Let the world know we're working.
    MessageBeep( MB_ICONINFORMATION );

    OutputDebugString( "TESTDLL: MyPolyTextOutA called.\n" );

    PolyTextOutA_Type OldFn = 
        (PolyTextOutA_Type)GDIHook.Functions[GDIFN_PolyTextOutA].OrigFn;
    return OldFn(hdc, pptxt, cStrings);
}

int WINAPI MyDrawTextA(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat)
{
	int nRet = winampAL.DrawTextUnicode(hDC, lpString, nCount, lpRect, uFormat);
	if (nRet == 0)
	{
		DrawTextA_Type OldFn = 
			(DrawTextA_Type)UserHook.Functions[UserFN_DrawTextA].OrigFn;
		return OldFn(hDC, lpString, nCount, lpRect, uFormat);
	}
	return nRet;
}

int WINAPI MyDrawTextExA(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat, LPDRAWTEXTPARAMS lpDTParams)
{
	int nRet = winampAL.DrawTextUnicode(hDC, lpString, nCount, lpRect, uFormat);
	if (nRet == 0)
	{
		DrawTextA_Type OldFn = 
			(DrawTextA_Type)UserHook.Functions[UserFN_DrawTextA].OrigFn;
		return OldFn(hDC, lpString, nCount, lpRect, uFormat);
	}
	return nRet;
}

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	hMod = (HMODULE)hModule;
    if ( ul_reason_for_call == DLL_PROCESS_ATTACH )  // When initializing....
	{
	}

    return TRUE;
}

// Winamp general plugin required functions
void config();
void quit();
int init();

// Microsoft GDIEXT Client DLL glue code
void MSIMG32_Free();
BOOL MSIMG32_Load();

// Microsoft Dial-Up Networking DLL glue code
void RASFree();
BOOL RASLoad();

// Microsoft Net DLL glue code
void NetFree();
BOOL NetLoad();

// Microsoft Net DLL glue code
void WinsockFree();
BOOL WinsockLoad();

// Microsoft URL Monitor DLL glue code
void URLMonFree();
BOOL URLMonLoad();

winampGeneralPurposePlugin plugin =
{
	GPPHDR_VER,
	"",
	init,
	config,
	quit,
};

int CALLBACK EnumFontFamExProc(ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, int FontType,  LPARAM lParam);
void AppendToWinampSystemMenu(HWND hWnd, HMENU hMenuWinamp)
{
	static HMENU hMenu			= NULL;
	static HMENU hMenuLanguage	= NULL;
	static HMENU hMenuEncoding	= NULL;
	static HMENU hMenuSort		= NULL;
	static HMENU hMenuTime		= NULL;

	// find position for inserting our menu
	int nPos=0;
	while (WINAMP_FILE_QUIT != GetMenuItemID(hMenuWinamp, nPos++));
	nPos -= 2;	// 2 up from the quit item

	// we already have the menu?
	if (hMenu)
	{
		// find our menu position
		while (nPos && (hMenu != GetSubMenu(hMenuWinamp, nPos--)));
		nPos++;

		// remove it from the winamp menu
		RemoveMenu(hMenuWinamp, nPos, MF_BYPOSITION);

		// destroy our menus
		DestroyMenu(hMenu);
		if (hMenuLanguage)	DestroyMenu(hMenuLanguage);
		if (hMenuEncoding)	DestroyMenu(hMenuEncoding);
		if (hMenuSort)		DestroyMenu(hMenuSort);
		if (hMenuTime)		DestroyMenu(hMenuTime);
	}

	HDC hdc = GetDC(hWnd);

	LOGFONT lf;

	BOOL bChineseBig5 = FALSE;
	BOOL bGB2312 = FALSE;
	BOOL bShiftJIS = FALSE;
	BOOL bHangul = FALSE;

	lf.lfFaceName[0] = 0;
	lf.lfPitchAndFamily = 0;

	// Traditional Chinese
	lf.lfCharSet = CHINESEBIG5_CHARSET;
	EnumFontFamiliesEx(hdc, &lf, (FONTENUMPROC)EnumFontFamExProc, (LPARAM)&bChineseBig5, 0);

	// Simplified Chinese
	lf.lfCharSet = GB2312_CHARSET;		
	EnumFontFamiliesEx(hdc, &lf, (FONTENUMPROC)EnumFontFamExProc, (LPARAM)&bGB2312, 0);

	// Japanese
	lf.lfCharSet = SHIFTJIS_CHARSET;	
	EnumFontFamiliesEx(hdc, &lf, (FONTENUMPROC)EnumFontFamExProc, (LPARAM)&bShiftJIS, 0);

	// Korean
	lf.lfCharSet = HANGUL_CHARSET;		
	EnumFontFamiliesEx(hdc, &lf, (FONTENUMPROC)EnumFontFamExProc, (LPARAM)&bHangul, 0);

	ReleaseDC(hWnd, hdc);
 	
	hMenu = CreatePopupMenu();

	BOOL bVisible = al_open;	//winampAL.IsWindowVisible();
	AppendMenu(hMenu, MF_STRING, bVisible ? ALBUMLIST_HIDE : ALBUMLIST_SHOW, bVisible ? ALSTRING("&Hide") : ALSTRING("S&how"));
	AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);

	// language sub-menu
	if (winampAL.m_LangArray.GetSize())
	{
		int sel = IDC_LANGUAGE_ENGLISH;

		hMenuLanguage = CreatePopupMenu();
		AppendMenu(hMenuLanguage, MF_STRING, IDC_LANGUAGE_ENGLISH, "English");

		int size = winampAL.m_LangArray.GetSize();
		for (int i=0; i<size; i++)
		{
			LPLANGINFO pLangInfo = (LPLANGINFO)winampAL.m_LangArray[i];
			AppendMenu(hMenuLanguage, MF_STRING, IDC_LANGUAGE+i, pLangInfo->language);
			if (lstrcmpi(szLangINI, pLangInfo->pathname) == 0)
			{
				sel = IDC_LANGUAGE + i;
			}
		}
		CheckMenuItem(hMenuLanguage, sel, MF_BYCOMMAND|MF_CHECKED);

		AppendMenu(hMenu, MF_POPUP, (UINT)hMenuLanguage, ALSTRING("&Language"));
	}

	// encoding sub-menu
	if (bChineseBig5 || bGB2312 || bShiftJIS || bHangul)
	{
		hMenuEncoding = CreatePopupMenu();
		AppendMenu(hMenuEncoding, MF_STRING, IDC_LANGUAGE_ENU, ALSTRING("&System Default"));
		if (bChineseBig5)	AppendMenu(hMenuEncoding, MF_STRING, IDC_LANGUAGE_CHT, ALSTRING("&Traditional Chinese"));
		if (bGB2312)		AppendMenu(hMenuEncoding, MF_STRING, IDC_LANGUAGE_CHS, ALSTRING("&Simplified Chinese"));
		if (bShiftJIS)		AppendMenu(hMenuEncoding, MF_STRING, IDC_LANGUAGE_JPN, ALSTRING("&Japanese"));
		if (bHangul)		AppendMenu(hMenuEncoding, MF_STRING, IDC_LANGUAGE_KOR, ALSTRING("&Korean"));

		CheckMenuItem(hMenuEncoding, al_language, MF_BYCOMMAND|MF_CHECKED);

		AppendMenu(hMenu, MF_POPUP, (UINT)hMenuEncoding, ALSTRING("&Encoding"));
	}

	// sort sub-menu
	hMenuSort = CreatePopupMenu();
	AppendMenu(hMenuSort, MF_STRING, IDC_SORT_OFF, ALSTRING("&Off"));
	AppendMenu(hMenuSort, MF_SEPARATOR, 0, NULL);
	AppendMenu(hMenuSort, MF_STRING, IDC_SORT_PATH, ALSTRING("Sort list by &path"));
	AppendMenu(hMenuSort, MF_STRING, IDC_SORT_NAME, ALSTRING("Sort list by &display name"));
	AppendMenu(hMenuSort, MF_STRING, IDC_SORT_ARTIST, ALSTRING("Sort list by &artist name"));
	AppendMenu(hMenuSort, MF_STRING, IDC_SORT_YEAR, ALSTRING("Sort list by &year"));
	AppendMenu(hMenuSort, MF_STRING, IDC_SORT_NUMOFSONGS, ALSTRING("Sort list by &number of tracks"));
	AppendMenu(hMenuSort, MF_STRING, IDC_SORT_TOTALTIME, ALSTRING("Sort list by album total &time"));
	AppendMenu(hMenuSort, MF_SEPARATOR, 0, NULL);
	AppendMenu(hMenuSort, MF_STRING, IDC_SORT_SHUFFLE, ALSTRING("&Shuffle albums"));

	CheckMenuItem(hMenuSort, al_sortalbum + IDC_SORT_OFF, MF_BYCOMMAND|MF_CHECKED);

	AppendMenu(hMenu, MF_POPUP, (UINT)hMenuSort, ALSTRING("&Sort"));

	// time sub-menu
	hMenuTime = CreatePopupMenu();
	AppendMenu(hMenuTime, MF_STRING, IDC_TIMEFORMAT0, ALSTRING("&Off"));
	AppendMenu(hMenuTime, MF_STRING, IDC_TIMEFORMAT1, ALSTRING("Show album total time in &h:mm:ss"));
	AppendMenu(hMenuTime, MF_STRING, IDC_TIMEFORMAT2, ALSTRING("Show album total time in &mmm:ss"));
	AppendMenu(hMenuTime, MF_SEPARATOR, 0, NULL);
	AppendMenu(hMenuTime, MF_STRING, IDC_TIMEFORMAT3, ALSTRING("Show &number of songs in album (nn)"));
	AppendMenu(hMenuTime, MF_STRING, IDC_SHOW_YEAR, ALSTRING("Show album &year - yyyy"));

	CheckMenuItem(hMenuTime, al_timeformat + IDC_TIMEFORMAT0, MF_BYCOMMAND|MF_CHECKED);
	if (al_shownum) CheckMenuItem(hMenuTime, IDC_TIMEFORMAT3, MF_BYCOMMAND|MF_CHECKED);
	if (al_showyear) CheckMenuItem(hMenuTime, IDC_SHOW_YEAR, MF_BYCOMMAND|MF_CHECKED);

	AppendMenu(hMenu, MF_POPUP, (UINT)hMenuTime, ALSTRING("Status &display"));

	AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
	AppendMenu(hMenu, MF_STRING, IDC_REFRESH, ALSTRING("Re&fresh", "\tCtrl+R"));
	AppendMenu(hMenu, MF_STRING, IDC_REFRESH_QUICK, ALSTRING("Refresh (&Quick)", "\tF5"));
	AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);

	AppendMenu(hMenu, MF_STRING, IDC_JUMPTOALBUM, ALSTRING("&Jump to album", "\tJ, F3"));
	AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);

	AppendMenu(hMenu, MF_STRING, IDC_SHOW_PROPERTIES, ALSTRING("P&roperties...", "\tCtrl+P"));

	if (al_instance)
	{
		char str[32];
		wsprintf(str, " (%ld)", al_instance);
		InsertMenu(hMenuWinamp, nPos, MF_BYPOSITION|MF_POPUP, (UINT)hMenu, ALSTRING("&Album List", str));
	}
	else
	{
		InsertMenu(hMenuWinamp, nPos, MF_BYPOSITION|MF_POPUP, (UINT)hMenu, ALSTRING("&Album List"));
	}
}

LRESULT (CALLBACK *lpWndProcOldPE)(HWND, UINT, WPARAM, LPARAM) = NULL;
LRESULT CALLBACK WndProcPE(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static RECT rc = { 0, 0, 0, 0 };
	if (message == WM_LBUTTONDOWN)
	{
		winampAL.InitDocking(PANEL_PE);
		GetClientRect(hwnd, &rc);
	}
	else if (message == WM_SIZE)
	{
		LRESULT lresult = CallWindowProc(lpWndProcOldPE, hwnd, message, wParam, lParam);

		int nHeight = HIWORD(lParam); // height of client area 
 
		if (((rc.bottom < 20) && (nHeight > 100)) ||
			((nHeight < 20) && (rc.bottom > 100)))
		{
			if (winampAL.IsDocked())
			{
				winampAL.MoveDocking(PANEL_PE);
			}
		}

		return lresult;
	}
	else if (message == WM_COMMAND)
	{
		WORD wNotifyCode = HIWORD(wParam); // notification code 
		WORD wID = LOWORD(wParam);         // item, control, or accelerator identifier 
		HWND hwndCtl = (HWND) lParam;      // handle of control 

		if (wID == WINAMP_NEXT_WINDOW)
		{
			winampAL.NextWindow(PANEL_PE);
			return TRUE;
		}
	}
	else if (message == WM_WA_IPC)
	{
		if (wParam == 666)
		{
			if (randskin) winampAL.RefreshSkin();
		}
	}

	return CallWindowProc(lpWndProcOldPE, hwnd, message, wParam, lParam);
}

LRESULT (CALLBACK *lpWndProcOldMB)(HWND, UINT, WPARAM, LPARAM) = NULL;
LRESULT CALLBACK WndProcMB(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static RECT rc = { 0, 0, 0, 0 };
	if (message == WM_LBUTTONDOWN)
	{
		winampAL.InitDocking(PANEL_MB);
		GetClientRect(hwnd, &rc);
	}
	else if (message == WM_SIZE)
	{
		LRESULT lresult = CallWindowProc(lpWndProcOldMB, hwnd, message, wParam, lParam);

		int nHeight = HIWORD(lParam); // height of client area 
 
		if (((rc.bottom < 20) && (nHeight > 100)) ||
			((nHeight < 20) && (rc.bottom > 100)))
		{
			if (winampAL.IsDocked())
			{
				winampAL.MoveDocking(PANEL_MB);
			}
		}

		return lresult;
	}
	else if (message == WM_COMMAND)
	{
		WORD wNotifyCode = HIWORD(wParam); // notification code 
		WORD wID = LOWORD(wParam);         // item, control, or accelerator identifier 
		HWND hwndCtl = (HWND) lParam;      // handle of control 

		if (wID == WINAMP_NEXT_WINDOW)
		{
			winampAL.NextWindow(PANEL_MB);
			return TRUE;
		}
	}

	return CallWindowProc(lpWndProcOldMB, hwnd, message, wParam, lParam);
}

LRESULT (CALLBACK *lpWndProcOldEQ)(HWND, UINT, WPARAM, LPARAM) = NULL;
LRESULT CALLBACK WndProcEQ(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static RECT rc = { 0, 0, 0, 0 };
	if (message == WM_LBUTTONDOWN)
	{
		winampAL.InitDocking(PANEL_EQ);
		GetClientRect(hwnd, &rc);
	}
	else if (message == WM_SIZE)
	{
		LRESULT lresult = CallWindowProc(lpWndProcOldEQ, hwnd, message, wParam, lParam);

		int nHeight = HIWORD(lParam); // height of client area 
 
		if (((rc.bottom < 20) && (nHeight > 100)) ||
			((nHeight < 20) && (rc.bottom > 100)))
		{
			if (winampAL.IsDocked())
			{
				winampAL.MoveDocking(PANEL_EQ);
			}
		}

		return lresult;
	}
	else if (message == WM_COMMAND)
	{
		WORD wNotifyCode = HIWORD(wParam); // notification code 
		WORD wID = LOWORD(wParam);         // item, control, or accelerator identifier 
		HWND hwndCtl = (HWND) lParam;      // handle of control 

		if (wID == WINAMP_NEXT_WINDOW)
		{
			winampAL.NextWindow(PANEL_EQ);
			return TRUE;
		}
	}

	return CallWindowProc(lpWndProcOldEQ, hwnd, message, wParam, lParam);
}

LRESULT (CALLBACK *lpWndProcOld)(HWND, UINT, WPARAM, LPARAM) = NULL;
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message == WM_LBUTTONDOWN)
	{
		winampAL.InitDocking(PANEL_MN);
	}
	else if (message == WM_MOVE || message == WM_SIZE)
	{
		LRESULT lresult = CallWindowProc(lpWndProcOld, hwnd, message, wParam, lParam);

		if (winampAL.IsDocked())
		{
			winampAL.MoveDocking(PANEL_MN);
		}

		if (wParam == SIZE_RESTORED)
		{
			if (!IsIconic(hwnd))
				winampAL.ShowWindow(SW_SHOW);
		}

		return lresult;
	}
	else if (message == WM_INITMENUPOPUP)
	{
		HMENU	hmenuPopup = (HMENU) wParam;         // handle to submenu
		UINT	uPos = (UINT) LOWORD(lParam);        // submenu item position 
		BOOL	fSystemMenu = (BOOL) HIWORD(lParam); // window menu flag 

		MENUITEMINFO mii;
		mii.cbSize = sizeof(MENUITEMINFO);
		mii.fMask = MIIM_ID;

		if (GetMenuItemInfo(hmenuPopup, 0, TRUE, &mii))
		{
			if (mii.wID == WINAMP_HELP_ABOUT)
			{
				AppendToWinampSystemMenu(hwnd, hmenuPopup);
			}
		}
	}
	else if ((message == WM_COMMAND) || (message == WM_SYSCOMMAND))
	{
		WORD wNotifyCode = HIWORD(wParam); // notification code 
		WORD wID = LOWORD(wParam);         // item, control, or accelerator identifier 
		HWND hwndCtl = (HWND) lParam;      // handle of control 
		LRESULT ret = 0;

		switch (wID)
		{
		case ALBUMLIST_HIDE:
			al_open = 0;
			winampAL.ShowWindow(SW_HIDE);
			break;

		case ALBUMLIST_SHOW:
			al_open = 1;
			winampAL.ShowWindow(SW_SHOW);
			break;

		case WINAMP_OPTIONS_EASYMOVE:
			easymove = !easymove;
			break;

		case WINAMP_OPTIONS_AOT:
			aot = !aot;
			if (hWndConfigPage)
			{
				SetWindowPos(hWndConfigPage, aot ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
			}
			break;

		case WINAMP_REFRESHSKIN:
			ret = CallWindowProc(lpWndProcOld, hwnd, message, wParam, lParam);
			winampAL.RefreshSkin();
			winampAL.Redraw();
			return ret;

		case WINAMP_NEXT_WINDOW:
			winampAL.NextWindow(PANEL_MN);
			return TRUE;

		case IDC_TIMEFORMAT3:
			al_shownum = !al_shownum;
			winampAL.Redraw();
			break;

		case IDC_SHOW_YEAR:
			al_showyear = !al_showyear;
			winampAL.Redraw();
			break;

		case IDC_SORT_OFF:
			al_sortalbum = wID - IDC_SORT_OFF;
			break;

		case IDC_REFRESH:
			winampAL.RefreshAlbums();
			break;

		case IDC_REFRESH_QUICK:
			winampAL.RefreshAlbums(TRUE/*QuickRefresh*/);
			break;

		case IDC_JUMPTOALBUM:
			DialogBox(plugin.hDllInstance, MAKEINTRESOURCE(IDD_JUMPTOALBUM), winampAL.GetSafeHwnd(), JumpToAlbumProc);
			break;

		case IDC_SHOW_PROPERTIES:
			config();
			break;

		default:
			if ((IDC_TIMEFORMAT0 <= wID) && (wID <= IDC_TIMEFORMAT2))
			{
				al_timeformat = wID - IDC_TIMEFORMAT0;
				winampAL.Redraw();
			}
			else if ((IDC_SORT_PATH <= wID) && (wID <= IDC_SORT_SHUFFLE))
			{
				al_sortalbum = wID - IDC_SORT_OFF;
				winampAL.Resort();
			}
			else if ((IDC_LANGUAGE_ENU <= wID) && (wID <= IDC_LANGUAGE_KOR))
			{
				al_language = wID;
				winampAL.Redraw();
			}
			else if (wID == IDC_LANGUAGE_ENGLISH)
			{
				lstrcpy(al_lang, "");
				lstrcpy(szLangINI, "");
			}
			else if ((IDC_LANGUAGE <= wID) && (wID <= IDC_LANGUAGE_LAST))
			{
				int idx = wID - IDC_LANGUAGE;
				if (idx < winampAL.m_LangArray.GetSize())
				{
					LPLANGINFO pLangInfo = (LPLANGINFO)winampAL.m_LangArray[idx];

					lstrcpy(al_lang, pLangInfo->filename);
					lstrcpy(szLangINI, pLangInfo->pathname);
				}
			}
		}
	}
	else if (message == WM_APPCOMMAND)
	{
		WORD dwCommand = GET_APPCOMMAND_LPARAM(lParam);
		switch (dwCommand)
		{
			case APPCOMMAND_MEDIA_PLAY_PAUSE: // Handles the Play/Pause Key
				if (SendMessage(hwnd, WM_WA_IPC, 0, IPC_ISPLAYING) == 1)
					PostMessage(hwnd, WM_COMMAND, WINAMP_BUTTON3/*pause*/, 0);
				else
					PostMessage(hwnd, WM_COMMAND, WINAMP_BUTTON2/*play*/, 0);
				return TRUE;

			case APPCOMMAND_MEDIA_STOP: // Handles the Stop Key
				PostMessage(hwnd, WM_COMMAND, WINAMP_BUTTON4/*stop*/, 0);
				return TRUE;

			case APPCOMMAND_MEDIA_PREVIOUSTRACK: // Handles the Previous Track Key
				PostMessage(hwnd, WM_COMMAND, WINAMP_BUTTON1/*prevtrack*/, 0);
				return TRUE;

			case APPCOMMAND_MEDIA_NEXTTRACK: // Handles the Next Track Key
				PostMessage(hwnd, WM_COMMAND, WINAMP_BUTTON5/*nexttrack*/, 0);
				return TRUE;

			default:
				return FALSE;
		}
	}
	else if (message == WM_WA_MPEG_EOF)
	{
		PostMessage(winampAL.GetSafeHwnd(), WM_COMMAND, IDC_PLAYBACK_STOPPED, 0);
	}
	else if (message == WM_CLOSE)
	{
		winampAL.Shutdown();
	}

	return CallWindowProc(lpWndProcOld, hwnd, message, wParam, lParam);
}

LRESULT (CALLBACK *lpWndProcOldDisp)(HWND, UINT, WPARAM, LPARAM) = NULL;
LRESULT CALLBACK WndProcDisp(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	WORD wNotifyCode = HIWORD(wParam);
	WORD wID = LOWORD(wParam);
	HWND hwndCtl = (HWND)lParam;

	switch (message)
	{
	case WM_COMMAND:
		if (wNotifyCode == EN_CHANGE)
		{
			char str[16] = "";
			GetWindowText(hwndCtl, str, 16);
			int nValue = atoi(str);

			switch (wID)
			{
			case IDC_PLFONTSIZE:
				pe_fontsize = nValue;
				if (pe_fontsize < 2) pe_fontsize = 10;
				winampAL.RefreshFont();
				winampAL.Redraw();
				break;

			case IDC_SNAPW:
				snaplen = nValue;
				break;
			}
		}
		else if (wNotifyCode == BN_CLICKED)
		{
			BOOL bChecked = IsDlgButtonChecked(hwnd, wID);
			switch (wID)
			{
			case IDC_SNAP:
				snap = bChecked;
				break;

			case IDC_PLNUMS:
				shownumsinpl = bChecked;
				winampAL.Redraw();
				break;

			case IDC_WA_CURSORS:
				usecursors = bChecked;
				break;

			case IDC_HIGHLIGHT:
				hilite = bChecked;
				winampAL.Redraw();
				break;

			case IDC_BIFONT:
				bifont = bChecked;
				winampAL.Redraw();
				break;
			}
		}
		break;
	}
	return CallWindowProc(lpWndProcOldDisp, hwnd, message, wParam, lParam);
}

LRESULT (CALLBACK *lpWndProcOldOptions)(HWND, UINT, WPARAM, LPARAM) = NULL;
LRESULT CALLBACK WndProcOptions(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	WORD wNotifyCode = HIWORD(wParam);
	WORD wID = LOWORD(wParam);
	HWND hwndCtl = (HWND)lParam;

	switch (message)
	{
	case WM_COMMAND:
		if (wNotifyCode == BN_CLICKED)
		{
			BOOL bChecked = IsDlgButtonChecked(hwnd, wID);
			switch (wID)
			{
			case IDC_CONVERT1:
				if (bChecked)	fixtitles |= 1;
				else			fixtitles &= ~1;
				winampAL.Redraw();
				break;

			case IDC_CONVERT2:
				if (bChecked)	fixtitles |= 2;
				else			fixtitles &= ~2;
				winampAL.Redraw();
				break;

			case IDC_AOVD:
				if (bChecked)	keeponscreen |= 2;
				else			keeponscreen &= ~2;
				winampAL.KeepOnScreen();
				break;
			}
		}
		break;
	}
	return CallWindowProc(lpWndProcOldOptions, hwnd, message, wParam, lParam);
}

LRESULT (CALLBACK *lpWndProcOldFileTypes)(HWND, UINT, WPARAM, LPARAM) = NULL;
LRESULT CALLBACK WndProcFileTypes(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	WORD wNotifyCode = HIWORD(wParam);
	WORD wID = LOWORD(wParam);
	HWND hwndCtl = (HWND)lParam;

	switch (message)
	{
	case WM_COMMAND:
		if (wNotifyCode == BN_CLICKED)
		{
			BOOL bChecked = IsDlgButtonChecked(hwnd, wID);
			switch (wID)
			{
			case IDC_ADDFILES:
				addtolist = bChecked;
				break;
			}
		}
		break;
	}
	return CallWindowProc(lpWndProcOldFileTypes, hwnd, message, wParam, lParam);
}

LRESULT (CALLBACK *lpWndProcOldSkins)(HWND, UINT, WPARAM, LPARAM) = NULL;
LRESULT CALLBACK WndProcSkins(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	WORD wNotifyCode = HIWORD(wParam);
	WORD wID = LOWORD(wParam);
	HWND hwndCtl = (HWND)lParam;

	switch (message)
	{
	case WM_COMMAND:
		if (wNotifyCode == BN_CLICKED)
		{
			BOOL bChecked = IsDlgButtonChecked(hwnd, wID);
			switch (wID)
			{
			case IDC_RANDOM:
				randskin = bChecked;
				break;
			}
		}
		break;
	}
	return CallWindowProc(lpWndProcOldSkins, hwnd, message, wParam, lParam);
}

#define MAX_CLASSNAME 64         // Length of buffer for retrieving the class name
HOOKPROC hkWndProc = NULL;
LRESULT CALLBACK CallWndProcRet(int nCode, WPARAM wParam, LPARAM lParam)
{     
	if (nCode == HC_ACTION)
	{
		CWPRETSTRUCT* pwp = (CWPRETSTRUCT*)lParam;
		if (pwp->message == WM_INITDIALOG)
		{
			HWND hWndParent = GetParent(pwp->hwnd);
			if (hWndParent)
			{
				TCHAR szBuf[MAX_CLASSNAME];
				GetClassName(hWndParent, szBuf, MAX_CLASSNAME);
				if (!lstrcmp(szBuf, "#32770"))
				{
					// is this the display option page?
					if (GetDlgItem(pwp->hwnd, IDC_PLFONTSIZE) &&
						GetDlgItem(pwp->hwnd, IDC_SNAPW) &&
						GetDlgItem(pwp->hwnd, IDC_SNAP) &&
						GetDlgItem(pwp->hwnd, IDC_PLNUMS))
					{
						lpWndProcOldDisp = (LRESULT(CALLBACK *)(HWND, UINT, WPARAM, LPARAM))GetWindowLong(pwp->hwnd, GWL_WNDPROC);
						SetWindowLong(pwp->hwnd, GWL_WNDPROC, (LONG)WndProcDisp);
					}
					// else, is this the option page?
					else
					if (GetDlgItem(pwp->hwnd, IDC_CONVERT1) &&
						GetDlgItem(pwp->hwnd, IDC_CONVERT2))
					{
						lpWndProcOldOptions = (LRESULT(CALLBACK *)(HWND, UINT, WPARAM, LPARAM))GetWindowLong(pwp->hwnd, GWL_WNDPROC);
						SetWindowLong(pwp->hwnd, GWL_WNDPROC, (LONG)WndProcOptions);
					}
					// else, is this the file types page?
					else
					if (GetDlgItem(pwp->hwnd, IDC_ADDFILES))
					{
						lpWndProcOldFileTypes = (LRESULT(CALLBACK *)(HWND, UINT, WPARAM, LPARAM))GetWindowLong(pwp->hwnd, GWL_WNDPROC);
						SetWindowLong(pwp->hwnd, GWL_WNDPROC, (LONG)WndProcFileTypes);
					}
					// else, is this the skins page?
					else
					if (GetDlgItem(pwp->hwnd, IDC_RANDOM) && 
						GetDlgItem(pwp->hwnd, IDC_CHDIR))
					{
						lpWndProcOldSkins = (LRESULT(CALLBACK *)(HWND, UINT, WPARAM, LPARAM))GetWindowLong(pwp->hwnd, GWL_WNDPROC);
						SetWindowLong(pwp->hwnd, GWL_WNDPROC, (LONG)WndProcSkins);
					}
				}
			}
		}
	}
	return CallNextHookEx((HHOOK)hkWndProc, nCode, wParam, lParam);
}

#define AL_URL		"http://albumlist.sourceforge.net/update.html"

HANDLE hCheckVerThread = NULL;
DWORD dwCheckVerThreadId = 0;

DWORD WINAPI CheckVerThreadProc(LPVOID lpParameter)
{
	// create watch connection
	CWatchConnection wc;
	wc.Refresh();

 	// if we are connected to the internet
	extern LONG g_fAbortDownload;
	while (!wc.IsConnected() && !g_fAbortDownload)
	{
		for (int i=0; i<120; i++)
		{
			Sleep(500);
			if (g_fAbortDownload) return 0;
		}
		wc.Refresh();
	}
	if (g_fAbortDownload) return 0;

	CCallback callback;

	// create temperary playlist
	char tempfile[MAX_PATH], temppath[MAX_PATH];
	if (GetTempPath(MAX_PATH, temppath) == 0) lstrcpy(temppath, ".\\");
	GetTempFileName(temppath, "wal", 0, tempfile);

	HRESULT hr = URLDownloadToFile(NULL, AL_URL, tempfile, 0, (LPBINDSTATUSCALLBACK)&callback);

	if (SUCCEEDED(hr))
	{
		HANDLE fp;
		if (INVALID_HANDLE_VALUE != (fp = CreateFile(tempfile, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)))
		{
			DWORD dwSize = GetFileSize (fp, NULL);

			char *buffer = new char [dwSize];

			DWORD nBytesRead;
			BOOL bResult = ReadFile(fp, buffer, dwSize, &nBytesRead, NULL);
			if (bResult)
			{
				// convert all the newline characters to NULL
				// for better processing of lines
				for (DWORD dwByte=0; dwByte<nBytesRead; dwByte++)
				{
					if (buffer[dwByte] == '\n') buffer[dwByte] = 0;
					else if (buffer[dwByte] == '\r') buffer[dwByte] = 0;
				}

				char *ptr = buffer;
				char *ptrend = buffer + nBytesRead;

				// skip the first line, find next non-null (HTML stuff)
				for (ptr += lstrlen(ptr); ((*ptr == 0) && (ptr < ptrend)); ptr++);

				// check header (GEN_M3A)
				if (0 == lstrcmp(ptr, "GEN_M3A"))
				{
					// skip this line, find next non-null (header line)
					for (ptr += lstrlen(ptr); ((*ptr == 0) && (ptr < ptrend)); ptr++);

					// get version from DLL
					int major = 1, minor = 20, build = 1;
					GetVersionInformation(major, minor, build);

					char szNum[10];
					char *p = ptr;

					int maj = 1, min = 20, bld = 1;

					// get major version
					while (*p++ != '.') ;
					lstrcpyn(szNum, ptr, p - ptr);
					maj = atoi(szNum);

					// check minor version
					ptr = p;
					while (*p++ != '.') ;
					lstrcpyn(szNum, ptr, p - ptr);
					min = atoi(szNum);

					// check build number
					bld = atoi(p);

					BOOL bNewVersion = FALSE;
					if (maj > major)		bNewVersion = TRUE;
					else if (maj == major)
					{
						if (min > minor)	bNewVersion = TRUE;
						else if (min == minor)
						{
							if (bld > build)	bNewVersion = TRUE;
						}
					}

					// skip this line, find next non-null (version line)
					for (ptr += lstrlen(ptr); ((*ptr == 0) && (ptr < ptrend)); ptr++);

					BOOL bShowDialog = FALSE;
					if (bNewVersion)
					{
						time_t t;
						time(&t);

						int ver = MAKELONG(MAKEWORD(bld, min), maj);

						// this is a new version, even newer than the one we checked
						// last time
						if (ver > al_newverchk)
						{
							bShowDialog = TRUE;
							al_newverchk = ver;
							al_lastverchk = t;
						}
						// make sure we are not poping up the new version dialog
						// too often.
						else if ((t - al_lastverchk) > TIME_BETWEEN_VERSION_CHECK)
						{
							bShowDialog = TRUE;
							al_lastverchk = t;
						}
					}

					if (bShowDialog)
					{
						char *message = new char [dwSize+1024];

						wsprintf(message,	"Album List %ld.%ld is now available for download!\n"
											"In the latest version of Album List:\n",
											maj, min);

						while (ptr < ptrend)
						{
							// since we are using geocities, it is adding a bunch
							// of crap at the end of the file
							if (lstrcmp(ptr, "END_GEN_M3A") == 0) break;

							lstrcat(message, ptr);
							lstrcat(message, "\n");

							// skip this line, find next non-null (version line)
							for (ptr += lstrlen(ptr); ((*ptr == 0) && (ptr < ptrend)); ptr++);
						}

						lstrcat(message, "\nWould you like to upgrade to the latest version?");

						if (MessageBox(NULL, message, ALSTRING("New Album List Available"), MB_YESNO) == IDYES)
						{
							HINSTANCE GotoURL(LPCTSTR url, int showcmd);
							GotoURL("http://albumlist.sourceforge.net", SW_SHOWNORMAL);
						}

						delete [] message;
					}
				}
			}

			delete [] buffer;

			// close the open file handle
			CloseHandle(fp);
		}
	}

	DeleteFile(tempfile);

	return 0;
}

void CheckForNewVersion()
{
	// make sure we are enough DLL to do this
	if (!bInternetReady) return;

	hCheckVerThread = CreateThread(NULL, 0, CheckVerThreadProc, 0, CREATE_SUSPENDED, &dwCheckVerThreadId);
	SetThreadPriority(hCheckVerThread, THREAD_PRIORITY_BELOW_NORMAL);
	ResumeThread(hCheckVerThread);
}

void SetUninstallString()
{
	HKEY hKey = NULL;
	DWORD openType;

	char szRegPath[MAX_PATH];
	wsprintf(szRegPath, "%s\\%s", REGSTR_PATH_UNINSTALL, szAppName);

	if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, szRegPath, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &openType) == ERROR_SUCCESS)
	{
		int major = 0, minor = 5, build = 1;
		GetVersionInformation(major, minor, build);

		char szBuffer[MAX_PATH];
		wsprintf(szBuffer, "%s v%ld.%ld (Remove Only)", szAppName, major, minor);
		RegSetValueEx(hKey, REGSTR_VAL_UNINSTALLER_DISPLAYNAME, 0, REG_SZ, (LPBYTE)szBuffer, lstrlen(szBuffer)+1);

		char szSystemDir[MAX_PATH];
		GetSystemDirectory(szSystemDir, MAX_PATH);

		char szModulePath[MAX_PATH], szModulePathLong[MAX_PATH];
		GetModuleFileName(plugin.hDllInstance, szModulePathLong, MAX_PATH);
		GetShortPathName(szModulePathLong, szModulePath, MAX_PATH);
 
		wsprintf(szBuffer, "RunDll32 %s,Uninstall", szModulePath);
		RegSetValueEx(hKey, REGSTR_VAL_UNINSTALLER_COMMANDLINE, 0, REG_SZ, (LPBYTE)szBuffer, lstrlen(szBuffer)+1);

		RegCloseKey(hKey);
	}
}

void config()
{	
	winampAL.Config();
}

static char oldPath[MAX_PATH] = "C:\\";

void quit()
{
	// kill the check version thread
	extern LONG g_fAbortDownload;
	g_fAbortDownload = 1;
	WaitForSingleObject(hCheckVerThread, 550);	// wait for 1/2 second
	CloseHandle(hCheckVerThread);

	// unload optional dynamic link libraries
	MSIMG32_Free();
	URLMonFree();
	RASFree();
	NetFree();
	WinsockFree();

	// unhook the window proc
	if (hkWndProc)
	{
		UnhookWindowsHookEx((HHOOK)hkWndProc);
		hkWndProc = NULL;
	}

	// write configuration file (AL settings)
	config_write();

	// write album list
	winampAL.WriteAlbumList();

	SetErrorMode(0);

	// restore directory
	SetCurrentDirectory(oldPath);
}

int init()
{
	al_instance = ALCountWindow("Winamp AL", NULL);

	GetCurrentDirectory(MAX_PATH, oldPath);

	if (al_instance == 0)
	{
//		HookAPICalls( &GDIHook );
		HookAPICalls( &UserHook );

		SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);

		// done in the NSIS script now! yay!
		//SetUninstallString();

		// load optional dynamic link libraries
		if (WinsockLoad() && URLMonLoad())
		{
			if (NetLoad()) bInternetReady = TRUE;
			if (RASLoad()) bInternetReady = TRUE;
		}
	}

	// create description string
	{
		static char c[512];
		int major = 0, minor = 5, build = 1;
		GetVersionInformation(major, minor, build);

		if (al_instance == 0)
		{
			// BASSFIX
			wsprintf((plugin.description=c), "%s v%ld.%ld Frozen Edition", szAppName, major, minor);
		}
		else
		{
			// BASSFIX
			wsprintf((plugin.description=c), "%s v%ld.%ld Frozen Edition (%ld)", szAppName, major, minor, al_instance);
		}
	}

	if (al_instance)
	{
		char str[32] = "";
		wsprintf(str, " %ld", al_instance);
		lstrcat(szAppName, str);
	}

	// read configuration files (both AL and Winamp settings)
	config_read();
	config_winamp_read();

	if (al_alphablend != 255)
	{
		if (!MSIMG32_Load())
		{
			al_alphablend = 255;
		}
	}

	HWND hWnd = NULL;

	if (hWnd = ALFindWindow("Winamp v1.x", NULL))
	{
		// check for new version
		if (al_newverchk && SendMessage(hWnd, WM_WA_IPC, 0, IPC_INETAVAILABLE))
			CheckForNewVersion();

		lpWndProcOld = (LRESULT(CALLBACK *)(HWND, UINT, WPARAM, LPARAM)) GetWindowLong(hWnd,GWL_WNDPROC);
		SetWindowLong(hWnd,GWL_WNDPROC,(LONG)WndProc);

		winampAL.Create(hWnd);

/*		wa_version = SendMessage(hWnd, WM_WA_IPC, 0, IPC_GETVERSION);

		// version 2.9 or above
		// add preference dialog to the options page
		if (wa_version >= 0x2090)
		{
			ALprefsDlgRec.hInst = hMod;
			ALprefsDlgRec.dlgID	= IDD_CONFIGTABS;
			ALprefsDlgRec.proc	= ConfigTabsProc;
			ALprefsDlgRec.name	= "Album List";
			ALprefsDlgRec.where	= 0;

			SendMessage(hWnd, WM_WA_IPC, (WPARAM)&ALprefsDlgRec, IPC_ADD_PREFS_DLG);
		}*/
	}

	if (hWnd = ALFindWindow("Winamp PE", NULL))
	{
		lpWndProcOldPE = (LRESULT(CALLBACK *)(HWND, UINT, WPARAM, LPARAM)) GetWindowLong(hWnd, GWL_WNDPROC);
		SetWindowLong(hWnd,GWL_WNDPROC,(LONG)WndProcPE);
	}

	if (hWnd = ALFindWindow("Winamp MB", NULL))
	{
		lpWndProcOldMB = (LRESULT(CALLBACK *)(HWND, UINT, WPARAM, LPARAM)) GetWindowLong(hWnd, GWL_WNDPROC);
		SetWindowLong(hWnd,GWL_WNDPROC,(LONG)WndProcMB);
	}

	if (hWnd = ALFindWindow("Winamp EQ", NULL))
	{
		lpWndProcOldEQ = (LRESULT(CALLBACK *)(HWND, UINT, WPARAM, LPARAM)) GetWindowLong(hWnd, GWL_WNDPROC);
		SetWindowLong(hWnd,GWL_WNDPROC,(LONG)WndProcEQ);
	}

	// install window hook
    hkWndProc = (HOOKPROC)SetWindowsHookEx( WH_CALLWNDPROCRET, (HOOKPROC) CallWndProcRet, NULL, GetCurrentThreadId());

	return 0;
}

extern "C"
__declspec( dllexport ) winampGeneralPurposePlugin * winampGetGeneralPurposePlugin()
{
	return &plugin;
}

void CALLBACK Uninstall(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
{
	plugin.hDllInstance = hMod;

	int major = 0, minor = 5, build = 1;
	GetVersionInformation(major, minor, build);

	char szBuffer[256];
	wsprintf(szBuffer, "Do you want to uninstall the plug-in\n"
					   "%s v%ld.%ld?", szAppName, major, minor);

	if (MessageBox(hwnd, szBuffer, "Uninstall", MB_YESNO) == IDNO)
	{
		// quit
		return;
	}

	// remove registry entry
	HKEY hKey = NULL;
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_UNINSTALL, 0, KEY_WRITE, &hKey) == ERROR_SUCCESS) 
	{
		RegDeleteKey(hKey, szAppName);
		RegCloseKey(hKey);
	}

	// get path
	char szModulePath[MAX_PATH];
	GetModuleFileName(hMod, szModulePath, MAX_PATH);
	char *p = (char *)_mbsrchr((unsigned char *)szModulePath,'\\');
	if (p) *p = 0;

	// remove skin bitmap
	char szFileName[MAX_PATH], szFileNameLong[MAX_PATH];
	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Skin\\albumlist.bmp");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	// remove skin text file
	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Skin\\albumlist.txt");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	// remove the Gen_m3a.Skin directory
	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Skin");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	RemoveDirectory(szFileName);

	// remove language packs
	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\catalan.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\czech.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\finnish.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\french.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\german.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\italian.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\japanese.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\polish.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\portuguese.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\russian.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang\\spanish.al");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	// remove the Gen_m3a.Lang directory
	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.Lang");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	RemoveDirectory(szFileName);

 	// remove readme file
	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.txt");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	// remove cached file
	wsprintf(szFileNameLong, "%s%s", szModulePath, "Gen_m3a.dat");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	DeleteFile(szFileName);

	// remove plugin.ini entries
	wsprintf(szFileNameLong, "%s%s", szModulePath, "Plugin.ini");
	GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
	WritePrivateProfileSection(szAppName, NULL, szFileName);

	// remove this DLL
	OSVERSIONINFO ver;
	ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	if (GetVersionEx(&ver))
	{
		GetModuleFileName(hMod, szFileNameLong, MAX_PATH);
		GetShortPathName(szFileNameLong, szFileName, MAX_PATH);
		if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT)
		{	// WinNT/2000
			MoveFileEx(szFileName, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
		}
		else if (ver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
		{	// Win95/98
			WritePrivateProfileString("Rename", "NUL", szFileName, "WININIT.INI");
		}

		// reboot the system?
		if (MessageBox(hwnd, "You must reboot the computer to complete the uninstall process.\n"
							 "Do you want to do it now?", "Uninstall", MB_YESNO) == IDYES)
		{
			ExitWindowsEx(EWX_REBOOT, 0);
		}

	}
}
