#include <windows.h>
#include <string.h>
#include "in2.h"
#include "dsp.h"
#include <mmsystem.h>

#define VERSION "1.08hack"

#define AUTHINFO \
"WA Plugin Interface DLL, v" VERSION " (c) 1999-2000 DragonZap, Hacked to mIRC 6.x+ by EmiZ"


#define WM_WA_MPEG_EOF WM_USER+2
#define MP3_PLUGIN_DESC "Nullsoft MPEG audio decoder plug-in "

#define TITLESIZE 500

#define AMPCLASS "Winamp v1.x"

// Returned error codes
#define WA_ERROR "ERROR WAE"
#define WAE_NOT_PLAYING		"0 Music is not playing"
#define WAE_PLAYING			"1 Music is currently playing"
#define WAE_BAD_FILE		"2 File could not be loaded"
#define WAE_NO_PLUGIN		"3 No matching plugin found"
#define WAE_HAVE_PLUGIN		"4 Plugin already loaded"
#define WAE_PLUGIN_VERSION	"5 Plugin is not correct version"
#define WAE_BAD_PLUGIN		"6 Plugin could not be loaded"
#define WAE_NO_OUT			"7 No output plugin loaded"
#define WAE_SYNTAX			"8 Error in request"
#define WAE_LONG_NAME		"9 Filename too long"
#define WAE_QUEUE_OOB		"10 Queue out of bounds"
#define WAE_QUEUE_EMPTY		"11 Queue is empty"
#define WAE_NO_SEEK			"12 This type of media cannot seek"
#define WAE_EXT_OOB			"13 Extension out of bounds"
#define WAE_IO				"14 I/O error"
#define WAE_INVALID_FORMAT	"15 Invalid file format"
#define WAE_USING_WINDOW	"16 Window is in use"
#define WAE_UNKNOWN			"255 Unknown error"
#define RETURNERROR(e) {\
	strcpy(data,WA_ERROR WAE_ ## e );\
	return 3;\
}


#ifdef __cplusplus
extern "C" {
#endif


struct PLUGINLIST {
	HMODULE WinampPlugin;
	In_Module *Plugin_Info;
	PLUGINLIST *Next,*Prev;
};

struct PLAYQUEUE {
	char FileName[MAX_PATH+1];
	char TitleCache[TITLESIZE];
	int SizeCache;
	PLAYQUEUE *Next,*Prev;
};

struct EQUALIZER {
	int Enabled;
	char Data[10];
	int PreAmplification;
};

EQUALIZER Equalizer = {
	0,
	{31,31,31,31,31,31,31,31,31,31},
	31
};

struct SPAWNCMD {
	char code;
	void * lpPlugin;
	char FileName[MAX_PATH+1];
};
#define WC_NOTHING			0	// Do nothing
#define WC_INPUT_INFO		1	// Spawn info dialog for specified file
#define WC_INPUT_ABOUT		2	// Spawn about box for input plugin
#define WC_INPUT_CONFIG		3	// Spawn config box for input plugin
#define WC_OUTPUT_ABOUT		4	// Spawn about box for output plugin
#define WC_OUTPUT_CONFIG	5	// Spawn config box for output plugin
#define WC_DSP_CONFIG		6	// Spawn config box for DSP plugin

char CurrentFile[MAX_PATH+1] = "";
PLAYQUEUE *PQStart = NULL,*PQEnd = NULL,*PQCurrent = NULL;

PLUGINLIST *First = NULL,*Last = NULL;
PLUGINLIST * MusicPlaying = NULL;

HMODULE OutPlugin = NULL;
Out_Module *OutPluginInfo = NULL;
HMODULE Self = NULL;
BOOL AutoShuffle = false;
BOOL Looping = false;
BOOL Installed = false;
HWND hFakeAmpWindow = NULL;
HWND hMircWindow = NULL;

HHOOK hHookUninstaller = NULL;

#define WN_NONE	0	// Don't notify mIRC on song completion
#define WN_WAVE 1	/* Notify mIRC with an "on WAVEEND" when a song completes; any current
                       wave dies */
#define WN_MIDI 2   /* Notify mIRC with an "on MIDIEND" when a song completes; any current
					   midi dies */
char NotifyMethod = WN_NONE;

winampDSPHeader * lpDSPHeader = NULL;
winampDSPModule * lpDSPModule = NULL;
HMODULE DSPPlugin = NULL;

SPAWNCMD wcSpawn = {
	WC_NOTHING,
	NULL,
	""
};

int soundVolume = 255;	// 0 to 255
int soundPan = 0;		// -128 to 128


PLAYQUEUE * InsertPrecachedQueue(char *File,char *Title,int length,PLAYQUEUE * After);
PLUGINLIST * AddInputPlugin(HMODULE PluginDLL,In_Module *Info);
PLUGINLIST * SelectInputPlugin(char * Filename,BOOL CheckURL);
BOOL RemoveInputPlugin(PLUGINLIST * Rem);
LRESULT CALLBACK AmpProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam);
void RemovePlayQueue(PLAYQUEUE * Die);
int CountPlayQueue();
BOOL StopMusic();
LRESULT CALLBACK AutoUninstall(int nCode,WPARAM wParam,LPARAM lParam);

HANDLE hSpawnEvent = NULL;
HANDLE hSpawnThread = NULL;

// Sends a command to the window spawner if it's not doing one already
char SendSpawnCmd(char cmd,void * plugin,char * file)
{
	char OldCmd = wcSpawn.code;
	if (OldCmd == WC_NOTHING)
	{
		if (file != NULL)
			strcpy(wcSpawn.FileName,file);
		wcSpawn.lpPlugin = plugin;

		// Set it in motion
		wcSpawn.code = cmd;

		SetEvent(hSpawnEvent);
	}
	return OldCmd;
}

DWORD WINAPI WindowSpawningThread(LPVOID lpParameter)
{
	SPAWNCMD *lpSpawn = (SPAWNCMD *)lpParameter;
	while(1)
	{
		switch(lpSpawn->code)
		{
		case WC_INPUT_INFO:
			((In_Module *)lpSpawn->lpPlugin)->InfoBox(lpSpawn->FileName,hMircWindow);
			lpSpawn->code = WC_NOTHING;
			break;
		case WC_INPUT_ABOUT:
			((In_Module *)lpSpawn->lpPlugin)->About(hMircWindow);
			lpSpawn->code = WC_NOTHING;
			break;
		case WC_INPUT_CONFIG:
			((In_Module *)lpSpawn->lpPlugin)->Config(hMircWindow);
			lpSpawn->code = WC_NOTHING;
			break;
		case WC_OUTPUT_ABOUT:
			((Out_Module *)lpSpawn->lpPlugin)->About(hMircWindow);
			lpSpawn->code = WC_NOTHING;
			break;
		case WC_OUTPUT_CONFIG:
			((Out_Module *)lpSpawn->lpPlugin)->Config(hMircWindow);
			lpSpawn->code = WC_NOTHING;
			break;
		case WC_DSP_CONFIG:
			((winampDSPModule *)lpSpawn->lpPlugin)->Config(
				(winampDSPModule *)lpSpawn->lpPlugin);
			lpSpawn->code = WC_NOTHING;
			break;
		}
		WaitForSingleObject(hSpawnEvent,10000);
	}
}

void SAVSAInit(int maxlatency_in_ms, int srate)
{
}

void SAVSADeInit()
{
}

void SAAddPCMData(void *PCMData, int nch, int bps, int timestamp)
{
}

int SAGetMode()
{
	return 2;
}

void SAAdd(void *data, int timestamp, int csa)
{
}

void VSAAddPCMData(void *PCMData, int nch, int bps, int timestamp)
{
}

int VSAGetMode(int *specNch, int *waveNch)
{
	return 0;
}

void VSAAdd(void *data, int timestamp)
{
}

void VSASetInfo(int nch, int srate)
{
}

	// dsp plug-in processing: 
	// (filled in by winamp, called by input plug)

	// returns 1 if active (which means that the number of samples returned by dsp_dosamples
	// could be greater than went in.. Use it to estimate if you'll have enough room in the
	// output buffer
int dsp_isactive()
{
	return (lpDSPModule != NULL);
}

	// returns number of samples to output. This can be as much as twice numsamples. 
	// be sure to allocate enough buffer for samples, then.
int dsp_dosamples(short int *samples, int numsamples, int bps, int nch, int srate)
{
	if (lpDSPModule != NULL)
		return lpDSPModule->ModifySamples(lpDSPModule,samples,numsamples,bps,nch,srate);
	else
		return numsamples;
}

int soundBitrate=0,soundSamplerate=0,soundStereo=0,soundSynched=0;
void SetInfo(int bitrate, int srate, int stereo, int synched)
{
	soundBitrate = bitrate;
	soundSamplerate = srate;
	soundStereo = stereo;
	soundSynched = synched;
}

// Resets the equalizer if there is music playing
void ResetEqualizer()
{
	if (MusicPlaying)
		MusicPlaying->Plugin_Info->EQSet(Equalizer.Enabled,
			Equalizer.Data,Equalizer.PreAmplification);
}

// Writes a line to a file, returns whatever WriteFile does
BOOL WriteLine(HANDLE File,char *Buf)
{
	DWORD tmp;
	BOOL Ok = WriteFile(File,Buf,strlen(Buf),&tmp,NULL);
	if (!Ok)
		return false;
	Ok = WriteFile(File,"\r\n",2,&tmp,NULL);
	return Ok;
}

// Reads a line from a file, returns TRUE if the entire line was read
int ReadLine(HANDLE File,char *Buf,int length,int *bufRead)
{
	int br = 0;
	char TheByte = 0;
	DWORD tmp;
	while(br < length)
	{
		if (!ReadFile(File,&TheByte,1,&tmp,NULL))
			return -1;
		if (tmp == 0)
		{
			if (Buf)
				Buf[br] = 0;
			if (bufRead)
				*bufRead = br;
			return 0;
		}

		if (TheByte == '\n')
		{
			// New line
			if (Buf)
				Buf[br] = 0;
			if (bufRead)
				*bufRead = br+1;
			return 0;
		}
		if (TheByte != '\r')
		{
			if (Buf)
				Buf[br] = TheByte;
			br++;
		}
	}
	if (Buf)
		Buf[br-1] = 0;
	if (bufRead)
		*bufRead = br;
	return 1;
}

// Returns a pointer to the loaded plugin's entry in the plugin list, or:
//					NULL	// Plugin is somehow bad
#define PE_HAVE		-1		// Plugin is already loaded
#define PE_VERSION	-2		// Plugin version mismatch
PLUGINLIST * LoadInputPlugin(char *FileName)
{
	HMODULE NewPlugin = LoadLibrary(FileName);

	In_Module *Plugin_Info;
	if (NewPlugin == NULL)
		return NULL;

	PLUGINLIST *Scan = First;
	while(Scan)
	{
		if (Scan->WinampPlugin == NewPlugin)
		{
			FreeLibrary(NewPlugin);
			return (PLUGINLIST *)PE_HAVE;
		}
		Scan = Scan->Next;
	}
	FARPROC winampGetInModule2 = GetProcAddress(NewPlugin,"winampGetInModule2");
	if (winampGetInModule2 == NULL)
	{
		FreeLibrary(NewPlugin);
		return NULL;
	}
	Plugin_Info =(In_Module *)winampGetInModule2();
	if (Plugin_Info == NULL)
	{
		FreeLibrary(NewPlugin);
		return NULL;
	}

	if (Plugin_Info->version != IN_VER)
	{
		FreeLibrary(NewPlugin);
		return (PLUGINLIST *)PE_VERSION;
	}

	PLUGINLIST * Ret = AddInputPlugin(NewPlugin,Plugin_Info);

	Plugin_Info->hMainWindow = hFakeAmpWindow;
	Plugin_Info->hDllInstance = NewPlugin;
	Plugin_Info->outMod = OutPluginInfo;

	// Now we copy over all our empty functions -- the vis functions are not supported
#define BLANK_COPY(func) Plugin_Info->func = func
	BLANK_COPY(SAVSAInit);
	BLANK_COPY(SAVSADeInit);
	BLANK_COPY(SAAddPCMData);
	BLANK_COPY(SAGetMode);
	BLANK_COPY(SAAdd);
	BLANK_COPY(VSAAddPCMData);
	BLANK_COPY(VSAGetMode);
	BLANK_COPY(VSAAdd);
	BLANK_COPY(VSASetInfo);
	BLANK_COPY(dsp_isactive);
	BLANK_COPY(dsp_dosamples);
	BLANK_COPY(SetInfo);
#undef BLANK_COPY

	Plugin_Info->Init();

	return Ret;
}

Out_Module * LoadOutputPlugin(char *FileName)
{
	if ((wcSpawn.code == WC_OUTPUT_ABOUT) || (wcSpawn.code == WC_OUTPUT_CONFIG))
		return NULL;

	HMODULE NewOutPlugin = LoadLibrary(FileName);
	Out_Module *Plugin_Info;

	if (NewOutPlugin == NULL)
		return NULL;

	FARPROC winampGetOutModule = GetProcAddress(NewOutPlugin,"winampGetOutModule");
	if (winampGetOutModule == NULL)
	{
		FreeLibrary(NewOutPlugin);
		return NULL;
	}

	Plugin_Info = (Out_Module *)winampGetOutModule();
	if (Plugin_Info == NULL)
	{
		FreeLibrary(NewOutPlugin);
		return NULL;
	}

	// Plugin version conflict
	if (Plugin_Info->version != OUT_VER)
	{
		FreeLibrary(NewOutPlugin);
		return (Out_Module *)PE_VERSION;
	}

	// If there's already an output plugin, deal with it...
	if (OutPlugin != NULL)
	{
		StopMusic();
		OutPluginInfo->Quit();
		FreeLibrary(OutPlugin);
	}
	OutPlugin = NewOutPlugin;
	OutPluginInfo = Plugin_Info;
	OutPluginInfo->hMainWindow = hFakeAmpWindow;
	OutPluginInfo->hDllInstance = NewOutPlugin;
	OutPluginInfo->Init();

	// Go through any existing input plugins and point them to the new output plugin
	PLUGINLIST * Current = First;
	while(Current)
	{
		Current->Plugin_Info->outMod = OutPluginInfo;
		Current->Plugin_Info->hMainWindow = hFakeAmpWindow; // Just in case
		Current = Current->Next;
	}

	return Plugin_Info;
}

int LoadDSPPlugin(char *FileName,int which)
{
	// It's ok to change DSP during playback, but not without first unloading
	// the existing one
	if ((DSPPlugin) || (lpDSPModule) || (lpDSPHeader))
		return PE_HAVE;

	HMODULE hNewDSP = LoadLibrary(FileName);
	if (!hNewDSP)
		return 0;

	FARPROC winampDSPGetHeader2 = GetProcAddress(hNewDSP,"winampDSPGetHeader2");
	if (!winampDSPGetHeader2)
	{
		FreeLibrary(hNewDSP);
		return 0;
	}
	
	winampDSPHeader * NewDSPHeader = (winampDSPHeader *)winampDSPGetHeader2();
	if ((!NewDSPHeader) || (NewDSPHeader->version != DSP_HDRVER))
	{
		FreeLibrary(hNewDSP);
		return PE_VERSION;
	}

	winampDSPModule * NewDSPModule = NewDSPHeader->getModule(which);

	if (!NewDSPModule)
	{
		FreeLibrary(hNewDSP);
		return 0;
	}

	NewDSPModule->hDllInstance = hNewDSP;
	// NewDSPModule->hwndParent = hFakeAmpWindow;

	/*	Why did I do this? That way the DSP window will stay in the foreground 
		in front of mIRC. */
	NewDSPModule->hwndParent = hMircWindow;

	if (NewDSPModule->Init(NewDSPModule))
	{
		FreeLibrary(hNewDSP);
		return 0;
	}


	// Insert new DSP module info
	DSPPlugin = hNewDSP;
	lpDSPHeader = NewDSPHeader;

	// Insert new DSP module
	lpDSPModule = NewDSPModule;

	return 1;
}

void UnloadDSPPlugin()
{
	winampDSPModule * OldModule = lpDSPModule;
	lpDSPModule = NULL;
	if (OldModule)		
		OldModule->Quit(OldModule);
	lpDSPHeader = NULL;
	if (DSPPlugin)
	{
		FreeLibrary(DSPPlugin);
		DSPPlugin = NULL;
	}
}

// Pass true to pause, returns true on success
BOOL PauseMusic(BOOL SetOn)
{
	if (MusicPlaying)
	{
		if (SetOn)
		{
			if (!MusicPlaying->Plugin_Info->IsPaused())
			{
				MusicPlaying->Plugin_Info->Pause();
				return true;
			}
		}
		else
			if (MusicPlaying->Plugin_Info->IsPaused())
			{
				MusicPlaying->Plugin_Info->UnPause();
				return true;
			}
	}
	return false;
}

// Plays a specified file, returns 0 on success
// -1 -- File not found
// -2 -- Output plugin not loaded
// -3 -- Correct plugin could not be found
// -4 -- Music is already playing
// -5 -- Some other error

int PlayMusic(char *Name)
{
	if(strlen(Name) > MAX_PATH)
		return -5;
	if (MusicPlaying)
		return -4;
	if (!OutPlugin)
	{
		PQCurrent = NULL;
		return -2;
	}
	char FileTitle[TITLESIZE];
	char AmpTitle[TITLESIZE+12];
	int SampleRate;
	OutPluginInfo->SetVolume(soundVolume);
	OutPluginInfo->SetPan(soundPan);

	// Find the correct plugin
	PLUGINLIST * Correct = SelectInputPlugin(Name,1);
	if (Correct == NULL)
	{
		PQCurrent = NULL;
		return -3;
	}
	Correct->Plugin_Info->GetFileInfo(Name,FileTitle,&SampleRate);
	strcpy(AmpTitle,"1. ");
	strcat(AmpTitle,FileTitle);
	strcat(AmpTitle," - Winamp");
	SendMessage(hFakeAmpWindow,WM_SETTEXT,0,(long)AmpTitle);
	int Playing = Correct->Plugin_Info->Play(Name);
	switch(Playing)
	{
	case 0:
	{
		char *lpszIgnore;
		if(!GetFullPathName(Name,MAX_PATH,CurrentFile,&lpszIgnore))
			strcpy(CurrentFile,Name);
		MusicPlaying = Correct;
		ResetEqualizer();
		return 0;
	}
	case -1:
		PQCurrent = NULL;
		return -1;
	default:
		PQCurrent = NULL;
		return -5;
	}
}

// Stops the music, returns true if music was playing
BOOL StopMusic()
{
	if (MusicPlaying)
	{
		MusicPlaying->Plugin_Info->Stop();
		MusicPlaying = NULL;
		return true;
	}
	return false;
}

// What do you think?
void ShufflePlayQueue()
{
	// First, count the queue entries
	int qCount = CountPlayQueue();

	// Ok, our algorithm is simple -- select a random number from 1 to qCount...
	// ...find that entry...
	// ...move it to the end, decrease qCount, and repeat until qCount is 0...

	// Random number seed
	srand((int)(hMircWindow) * GetTickCount());
	while(qCount > 0)
	{
		PLAYQUEUE * Current = PQStart;
		int randNum = rand() % qCount;
		int scanList = 0;
		while(scanList < randNum)
		{
			scanList++;
			Current = Current->Next;
		}

		// This is the one we're moving to the end.
		if (Current->Prev == NULL)
			PQStart = Current->Next;
		if (Current->Next == NULL)
			PQEnd = Current->Prev;
		if (Current->Next)
			Current->Next->Prev = Current->Prev;
		if (Current->Prev)
			Current->Prev->Next = Current->Next;

		// And reattach it to the end
		Current->Next = NULL;
		Current->Prev = PQEnd;
		if (PQStart == NULL)
			PQStart = Current;
		if (PQEnd != NULL)
			PQEnd->Next = Current;
		PQEnd = Current;

		qCount--;
	}
}

void ClearPlayQueue()
{
	if (PQCurrent != NULL)
		StopMusic();
	while(PQStart)
		RemovePlayQueue(PQStart);
	PQCurrent = NULL;
}

#define QE_OK		0
#define QE_OOB		-1
#define QE_INVALID	-2

// Selects an item from the queue based on "Which"
int LastQueueRequest = 1;

int SelectQueueItem(const char *Which,LPVOID *Result)
{
	if ((Which[0] < '0') || (Which[0] > '9'))
	{
		// It's not a number, so check it for other strings
		char * WE = strchr(Which,' ');
		int WordSize = 0;
		if (WE == NULL)
			WordSize = strlen(Which);
		else
			WordSize = WE - Which;
		if (!strnicmp(Which,"current",WordSize))
		{
			// Current queue pointer
			*Result = (LPVOID)PQCurrent;
			return (PQCurrent == NULL ? QE_OOB : QE_OK);
		}
		if (!strnicmp(Which,"next",WordSize))
		{
			if (PQCurrent == NULL)
			{
				*Result = (LPVOID)PQStart;
				return QE_OOB;
			}
			*Result = (LPVOID)PQCurrent->Next;
			return (PQCurrent->Next == NULL ? QE_OOB : QE_OK);
		}
		if (!strnicmp(Which,"previous",WordSize))
		{
			if (PQCurrent == NULL)
			{
				*Result = NULL;
				return QE_OOB;
			}
			*Result = (LPVOID)PQCurrent->Prev;
			return (PQCurrent->Prev == NULL ? QE_OOB : QE_OK);
		}
		if (!strnicmp(Which,"last",WordSize))
		{
			*Result = (LPVOID)PQEnd;
			return (PQEnd == NULL ? QE_OOB : QE_OK);
		}
		return QE_INVALID;
	}
	unsigned int WhichQueue = atoi(Which);
	if (WhichQueue < 1)
	{
		*Result = (LPVOID)PQStart;
		return QE_OOB;
	}
	unsigned int ScanQueue = 1;
	PLAYQUEUE * FindQueue = PQStart;
	while((FindQueue) && (ScanQueue < WhichQueue))
	{
		ScanQueue++;
		FindQueue = FindQueue->Next;
	}
	
	if (FindQueue == NULL)
	{
		*Result = (LPVOID)PQEnd;
		return QE_OOB;
	}
	*Result = (LPVOID)FindQueue;
	return QE_OK;
}

int CountPlayQueue()
{
	PLAYQUEUE *Current = PQStart;
	int Count = 0;
	while(Current)
	{
		Count++;
		Current = Current->Next;
	}
	return Count;
}

// Simple. Uninstallation.
void UninstallThisDLL()
{

	StopMusic();
	
	ClearPlayQueue();

	if (hSpawnThread)
		TerminateThread(hSpawnThread,0);

	UnloadDSPPlugin();

	while(First)
		RemoveInputPlugin(First);

	if(OutPluginInfo)
	{
		OutPluginInfo->Quit();
		OutPluginInfo = NULL;
	}

	if(OutPlugin)
	{
		FreeLibrary(OutPlugin);
		OutPlugin = NULL;
	}

	Installed = 0;
}


BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{	
	Self = hinstDLL;

	switch(fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		{
			WNDCLASS AmpClass;
			memset(&AmpClass,0,sizeof(WNDCLASS));
			AmpClass.lpszClassName = AMPCLASS;
			AmpClass.hInstance = Self;
			AmpClass.lpfnWndProc = AmpProc;
			RegisterClass(&AmpClass);

		}
		break;
	case DLL_PROCESS_DETACH:
		if (hSpawnEvent)
			CloseHandle(hSpawnEvent);

		if (hHookUninstaller != NULL)
			UnhookWindowsHookEx(hHookUninstaller);

		// Unregister the window class
		UnregisterClass("Winamp v1.x",Self);

		break;
	}
	return 1;
}

BOOL StayLoaded(HWND hMircWin)
{
	char MyName[MAX_PATH];
	if (!Installed)
	{
		// Reload library
		GetModuleFileName(Self,MyName,MAX_PATH);
		LoadLibrary(MyName);

		if (!hFakeAmpWindow)
			hFakeAmpWindow = CreateWindow(
				AMPCLASS,
				"WinAmp 2.5e",
				WS_CAPTION,
				0,0,
				200,200,
				hMircWindow,
				NULL,
				Self,
				NULL);		

		hMircWindow = hMircWin;

		hSpawnEvent = CreateEvent(NULL,false,false,NULL);

		DWORD dwThreadId;
		hSpawnThread = CreateThread(NULL,0,WindowSpawningThread,&wcSpawn,0,&dwThreadId);

		hHookUninstaller = SetWindowsHookEx(WH_CALLWNDPROC,AutoUninstall,Self,GetCurrentThreadId());

		Installed = 1;
		return 1;
	}
	return 0;
}

PLUGINLIST * AddInputPlugin(HMODULE PluginDLL,In_Module *Info)
{
	PLUGINLIST * NewPlugin = new PLUGINLIST;
	if (NewPlugin == NULL)
		return NULL;
	NewPlugin->WinampPlugin = PluginDLL;
	NewPlugin->Plugin_Info = Info;
	NewPlugin->Next = NULL;

	if (First == NULL)
	{
		First = NewPlugin;
		NewPlugin->Prev = NULL;
	}
	else {
		NewPlugin->Prev = Last;
		Last->Next = NewPlugin;
	}
	Last = NewPlugin;
	return NewPlugin;
}

// Insert a file into the play queue, after "After" if it is not NULL
PLAYQUEUE * InsertPlayQueue(char *File,PLAYQUEUE * After)
{
	int length;
	char FileName[MAX_PATH+1];
	char Title[TITLESIZE];
	char *tmp;
	if(!GetFullPathName(File,MAX_PATH,FileName,&tmp))
		strcpy(FileName,File);
	PLUGINLIST *InPlugin = SelectInputPlugin(File,true);
	if (InPlugin)
		InPlugin->Plugin_Info->GetFileInfo(File,Title,&length);
	else
		strcpy(Title,File);
	return InsertPrecachedQueue(FileName,Title,length,After);
}

PLAYQUEUE * InsertPrecachedQueue(char *File,char *Title,int length,PLAYQUEUE * After)
{
	if (strlen(File) > MAX_PATH)
		return NULL;
	PLAYQUEUE * NewQueue = new PLAYQUEUE;
	if (!NewQueue)
		return NULL;
	if (After)
	{
		NewQueue->Next = After->Next;
		NewQueue->Prev = After;
		if (After->Next)
			After->Next->Prev = NewQueue;
		else
			PQEnd = NewQueue;
		After->Next = NewQueue;
	}
	else
	{
		// Insert first
		NewQueue->Prev = NULL;
		NewQueue->Next = PQStart;
		if (PQStart)
			PQStart->Prev = NewQueue;
		if (NewQueue->Next == NULL)
			PQEnd = NewQueue;
		PQStart = NewQueue;
	}
	strcpy(NewQueue->FileName,File);
	strcpy(NewQueue->TitleCache,Title);
	NewQueue->SizeCache = length;
	return NewQueue;
}

// Remove a file from the playqueue
void RemovePlayQueue(PLAYQUEUE * Die)
{
	if (Die->Prev == NULL)
		PQStart = Die->Next;
	if (Die->Next == NULL)
		PQEnd = Die->Prev;
	if (Die->Next)
		Die->Next->Prev = Die->Prev;
	if (Die->Prev)
		Die->Prev->Next = Die->Next;

	bool WasCurrent = false;
	if (PQCurrent == Die)
	{
		PQCurrent = Die->Next;
		
		WasCurrent = true;
	}
	// Move to the next music
	if (MusicPlaying && WasCurrent)
	{
		BOOL Paused = (MusicPlaying->Plugin_Info->IsPaused() != 0);
		StopMusic();
		if (PQCurrent)
		{
			PlayMusic(Die->Next->FileName);
			PauseMusic(Paused);
		}
	}
	delete Die;
}

// Finds the number of the specified input plugin, or 0 if not found
int FindInputPluginNumber(In_Module *Which)
{
	int pNum = 0;
	PLUGINLIST *Current = First;
	while(Current)
	{
		pNum++;
		if (Which == Current->Plugin_Info)
			return pNum;
		Current = Current->Next;
	}
	return 0;
}

PLUGINLIST * PickNumberedInputPlugin(char *Which)
{
	int PNumber = atoi(Which);
	if (PNumber < 1)
		return NULL;
	int PScan = 1;
	PLUGINLIST *Current = First;
	while(Current && (PScan < PNumber))
	{
		PScan++;
		Current = Current->Next;
	}
	return Current;
}

// Selects a plugin based on the filename extension and whatever
PLUGINLIST * SelectInputPlugin(char * FileName,BOOL CheckURL)
{
	PLUGINLIST * Current = First;

	// Check URL-like filenames first
	if (CheckURL)
		while(Current)
		{
			if (Current->Plugin_Info->IsOurFile(FileName))
				return Current;
			Current = Current->Next;
		}

	// Now check by extension
	Current = First;
	while(Current)
	{
		// Scan the characters
		const char * Extensions = Current->Plugin_Info->FileExtensions;

		// We'll want to skip every other
		int ExtLen;
		while((ExtLen = strlen(Extensions)) != 0)
		{
			// Scan for ;
			for(int x=0;x<ExtLen;x++)
				if (Extensions[x] == ';')
				{
					ExtLen = x;
					break;
				}

			int NameLen = strlen(FileName);
			if ((NameLen > ExtLen) &&
				(!strnicmp(FileName + NameLen - ExtLen,Extensions,ExtLen)) &&
				(FileName[NameLen-ExtLen-1] == '.'))
					return Current;
			if (Extensions[ExtLen] != ';')
			{
				Extensions += ExtLen + 1;
				Extensions += strlen(Extensions) + 1;
			}
			else
				Extensions += ExtLen + 1;
		}

		Current = Current->Next;
	}

	// When all else fails, use the MP3 plugin
	if (CheckURL)
	{
		unsigned int DescLen = strlen(MP3_PLUGIN_DESC);
		Current = First;
		while(Current)
		{
			if (!strnicmp(MP3_PLUGIN_DESC,Current->Plugin_Info->description,DescLen))
				return Current;
			Current = Current->Next;
		}
	}
	return NULL;
}

BOOL RemoveInputPlugin(PLUGINLIST * Rem)
{
	if (Rem == NULL)
		return 0;
	if (Rem == First)
		First = Rem->Next;
	if (Rem == Last)
		Last = Rem->Prev;
	if (Rem->Prev != NULL)
		Rem->Prev->Next = Rem->Next;
	if (Rem->Next != NULL)
		Rem->Next->Prev = Rem->Prev;
	Rem->Plugin_Info->Quit();
	FreeLibrary(Rem->WinampPlugin);
	delete Rem;
	return 1;
}


LRESULT CALLBACK AutoUninstall(int nCode,WPARAM wParam,LPARAM lParam)
{
	if (nCode < 0)
		return CallNextHookEx(hHookUninstaller,nCode,wParam,lParam);
	CWPSTRUCT * msg = (CWPSTRUCT *)lParam;

	if ((msg->message == WM_DESTROY) && (msg->hwnd == hMircWindow))
		DestroyWindow(hFakeAmpWindow);
	
	return CallNextHookEx(hHookUninstaller,nCode,wParam,lParam);
}

LRESULT CALLBACK AmpProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)
{
	switch(Msg)
	{
	case WM_DESTROY:
		hFakeAmpWindow = NULL;
		
		UninstallThisDLL();
		break;
	case WM_USER:
		switch(lParam)
		{
		case 104:
			// Play test -- return 0 if not playing, 1 if playing
			if (MusicPlaying)
				return 1;
			return 0;
		case 0x190:
			// Other play test -- return 1 if using the queue, not sure
			// what it's really supposed to be, but seems to work.
			if (PQCurrent)
				return 1;
		}
		break;
	case WM_WA_MPEG_EOF:
		// The current song has ended...
		StopMusic();

		// Let mIRC know; pretend it's a midi finishing
		char DevID[100];
		switch(NotifyMethod)
		{
		case WN_MIDI:
			mciSendString("close mircmidi",NULL,0,hMircWindow);
			mciSendString("open sequencer alias mircmidi",DevID,99,hMircWindow);
			PostMessage(hMircWindow,MM_MCINOTIFY,MCI_NOTIFY_SUCCESSFUL,atoi(DevID));
			break;
		case WN_WAVE:
			mciSendString("close mircwave",NULL,0,hMircWindow);
			mciSendString("open waveaudio alias mircwave",DevID,99,hMircWindow);
			PostMessage(hMircWindow,MM_MCINOTIFY,MCI_NOTIFY_SUCCESSFUL,atoi(DevID));
			break;
		}
		if (PQCurrent != NULL)
		{
			PQCurrent = PQCurrent->Next;
			if (AutoShuffle && !PQCurrent)
				ShufflePlayQueue();
			if (Looping && !PQCurrent)
				PQCurrent = PQStart;
			if (PQCurrent)
				PlayMusic(PQCurrent->FileName);
		}
		break;
	default:
		return DefWindowProc(hWnd,Msg,wParam,lParam);
	}
	return 0;
}

int FAR PASCAL CurrentMusic(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	if (!stricmp(data,"status"))
	{
		if (!MusicPlaying)
			strcpy(data,"OK stopped");
		else if (MusicPlaying->Plugin_Info->IsPaused())
			strcpy(data,"OK paused");
		else
			strcpy(data,"OK playing");
		return 3;
	}
	if (!MusicPlaying)
		RETURNERROR(NOT_PLAYING);
	if (!stricmp(data,"pause"))
	{
		if (!PauseMusic(true))
			RETURNERROR(NOT_PLAYING);
		strcpy(data,"OK paused");
		return 3;
	}
	else if (!stricmp(data,"unpause"))
	{
		if (!PauseMusic(false))
			RETURNERROR(PLAYING);
		strcpy(data,"OK unpaused");
		return 3;
	}
	else if (!stricmp(data,"title"))
	{
		int tmp;
		strcpy(data,"OK ");
		MusicPlaying->Plugin_Info->GetFileInfo("",data+3,&tmp);
		return 3;
	}
	else if (!stricmp(data,"filename"))
	{
		strcpy(data,"OK ");
		strcpy(data+3,CurrentFile);
		return 3;
	}
	else if (!stricmp(data,"position"))
	{
		strcpy(data,"OK ");
		itoa(MusicPlaying->Plugin_Info->GetOutputTime(),data+3,10);
		return 3;
	}
	else if (!stricmp(data,"length"))
	{
		strcpy(data,"OK ");
		itoa(MusicPlaying->Plugin_Info->GetLength(),data+3,10);
		return 3;
	}
	else if (!strnicmp(data,"seek ",5))
	{
		if (!MusicPlaying->Plugin_Info->is_seekable)
			RETURNERROR(NO_SEEK);
		MusicPlaying->Plugin_Info->SetOutputTime(atoi(strchr(data,' ')+1));
		strcpy(data,"OK seeked");
		return 3;
	}
	else if (!stricmp(data,"bitrate"))
	{
		strcpy(data,"OK ");
		itoa(soundBitrate,data+3,10);
		return 3;
	}
	else if (!stricmp(data,"samplerate"))
	{
		strcpy(data,"OK ");
		itoa(soundSamplerate,data+3,10);
		return 3;
	}
	else if (!stricmp(data,"stereo"))
	{
		strcpy(data,"OK ");
		itoa(soundStereo,data+3,10);
		return 3;
	}
	else if (!stricmp(data,"synched"))
	{
		strcpy(data,"OK ");
		strcpy(data+3,(soundSynched ? "yes" : "no"));
		return 3;
	}
	else if (!stricmp(data,"info"))
	{
		if (SendSpawnCmd(WC_INPUT_INFO,MusicPlaying->Plugin_Info,CurrentFile) !=
			WC_NOTHING)
			RETURNERROR(USING_WINDOW);
		strcpy(data,"OK infobox");
		return 3;
	}
	RETURNERROR(SYNTAX);
}

int FAR PASCAL PluginManager(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	StayLoaded(mWnd);

	char * Space = strchr(data,' ');
	if (!stricmp(data,"inputcount"))
	{
		int InputPluginCount = 0;
		PLUGINLIST * Current = First;
		while(Current)
		{
			InputPluginCount++;
			Current = Current->Next;
		}
		strcpy(data,"OK ");
		itoa(InputPluginCount,data+3,10);
		return 3;
	}
	else if (!strnicmp(data,"uinfo ",6))
	{
		// Retrieve info on unloaded plugin
// Base plugin types
#define PI_OUTPUT	0x00
#define PI_INPUT	0x01
#define PI_DSP		0x02
#define PI_VIS		0x03	// Unused

#define PI_ALL		0x07	// Combines all previous

// Subfunction info types
#define PI_S_NAME		0x00	// Plugin description
#define PI_S_MODNAME	0x08	// Plugin module description
#define PI_S_VERSION	0x10	// Plugin version
#define PI_S_MODCOUNT	0x18	// Module count
#define PI_S_ID			0x20	// Output module ID

#define PI_S_ALL		0xF8	// Combines all previous

		char InfoType =0;
		Space++;
		if (!strnicmp(Space,"output ",7))
			InfoType = PI_OUTPUT;
		else if (!strnicmp(Space,"input ",6))
			InfoType = PI_INPUT;
		else if (!strnicmp(Space,"dsp ",4))
			InfoType = PI_DSP;
/*		else if (!strnicmp(Space,"vis ",4))
			InfoType = PI_VIS;*/
		else
			RETURNERROR(SYNTAX);

		Space = strchr(Space,' ')+1;	// No need for NULL checking, we know it's there
		if (!strnicmp(Space,"name ",5))
			InfoType |= PI_S_NAME;
		else if (!strnicmp(Space,"modname ",8))
			InfoType |= PI_S_MODNAME;
		else if (!strnicmp(Space,"version ",8))
			InfoType |= PI_S_VERSION;
		else if (!strnicmp(Space,"modcount ",9))
			InfoType |= PI_S_MODCOUNT;
		else if (!strnicmp(Space,"id ",3))
			InfoType |= PI_S_ID;
		else
			RETURNERROR(SYNTAX);

		// Point Space to the filename
		Space = strchr(Space,' ')+1;
		int WhichMod = -1;

		// Extra processing when a number is required
		if ((InfoType & PI_S_ALL) == PI_S_MODNAME)
		{
			if ((*Space < '0') || (*Space > '9'))
				RETURNERROR(SYNTAX);
			WhichMod = atoi(Space);
			Space = strchr(Space,' ');
			if (Space == NULL)
				RETURNERROR(SYNTAX);
			Space++;
		}

		// Check for valid pairings
		switch(InfoType)
		{
		case PI_OUTPUT | PI_S_NAME:
		case PI_OUTPUT | PI_S_VERSION:
		case PI_OUTPUT | PI_S_ID:
		case PI_INPUT | PI_S_NAME:
		case PI_INPUT | PI_S_VERSION:
		case PI_DSP | PI_S_NAME:
		case PI_DSP | PI_S_MODNAME:
		case PI_DSP | PI_S_VERSION:
		case PI_DSP | PI_S_MODCOUNT:
			break;
		default:
			RETURNERROR(SYNTAX);
		}
		
		HMODULE hInfoPlugin = LoadLibrary(Space);
		if (!hInfoPlugin)
			RETURNERROR(NO_PLUGIN);
		switch(InfoType & PI_ALL)
		{
		case PI_OUTPUT:
			{
			FARPROC winampGetOutModule = GetProcAddress(hInfoPlugin,"winampGetOutModule");
			if (winampGetOutModule == NULL)
			{
				FreeLibrary(hInfoPlugin);
				RETURNERROR(BAD_PLUGIN)
			}

			Out_Module * Plugin_Info = (Out_Module *)winampGetOutModule();
			if (Plugin_Info == NULL)
			{
				FreeLibrary(hInfoPlugin);
				RETURNERROR(BAD_PLUGIN);
			}

			if ((InfoType & PI_S_ALL) == PI_S_VERSION)
			{
				strcpy(data,"OK ");
				itoa(Plugin_Info->version,data+3,16);

				// Insert hex point
				int slen = strlen(data);
				for(int mv=slen+1;mv>slen-1;mv--)
					data[mv] = data[mv-1];
				data[slen-1] = '.';
				return 3;
			}
			// Plugin version conflict
			if (Plugin_Info->version != OUT_VER)
			{
				FreeLibrary(hInfoPlugin);
				RETURNERROR(PLUGIN_VERSION);
			}
			switch(InfoType & PI_S_ALL)
			{
			case PI_S_NAME:
				strcpy(data,"OK ");
				strcpy(data+3,Plugin_Info->description);
				break;
			case PI_S_ID:
				strcpy(data,"OK ");
				itoa(Plugin_Info->id,data+3,10);
				break;
			}

			}
			break;
		case PI_INPUT:
			{
				FARPROC winampGetInModule2 = GetProcAddress(hInfoPlugin,"winampGetInModule2");
				if (winampGetInModule2 == NULL)
				{
					FreeLibrary(hInfoPlugin);
					RETURNERROR(BAD_PLUGIN);
				}
				In_Module * Plugin_Info =(In_Module *)winampGetInModule2();
				if (Plugin_Info == NULL)
				{
					FreeLibrary(hInfoPlugin);
					RETURNERROR(BAD_PLUGIN);
				}
				if ((InfoType & PI_S_ALL) == PI_S_VERSION)
				{
					strcpy(data,"OK ");
					itoa(Plugin_Info->version,data+3,16);

					// Insert hex point
					int slen = strlen(data);
					for(int mv=slen+1;mv>slen-2;mv--)
						data[mv] = data[mv-1];
					data[slen-2] = '.';
					return 3;
				}
				if (Plugin_Info->version != IN_VER)
				{
					FreeLibrary(hInfoPlugin);
					RETURNERROR(PLUGIN_VERSION);
				}
				switch(InfoType & PI_S_ALL)
				{
				case PI_S_NAME:
					strcpy(data,"OK ");
					strcpy(data+3,Plugin_Info->description);
					break;
				}
			}
			break;
		case PI_DSP:
			{
			FARPROC winampDSPGetHeader2 = GetProcAddress(hInfoPlugin,"winampDSPGetHeader2");
			if (!winampDSPGetHeader2)
			{
				FreeLibrary(hInfoPlugin);
				RETURNERROR(BAD_PLUGIN);
			}
			winampDSPHeader * DspCHeader = (winampDSPHeader *)winampDSPGetHeader2();
			if (!DspCHeader)
			{
				FreeLibrary(hInfoPlugin);
				RETURNERROR(BAD_PLUGIN);
			}
			if ((InfoType & PI_S_ALL) == PI_S_VERSION)
			{
				strcpy(data,"OK ");
				itoa(DspCHeader->version,data+3,16);

				// Insert hex point
				int slen = strlen(data);
				for(int mv=slen+1;mv>slen-1;mv--)
					data[mv] = data[mv-1];
				data[slen-1] = '.';
				return 3;
			}
			if (DspCHeader->version != DSP_HDRVER)
			{
				FreeLibrary(hInfoPlugin);
				RETURNERROR(PLUGIN_VERSION);
			}
			switch(InfoType & PI_S_ALL)
			{
			case PI_S_NAME:
				strcpy(data,"OK ");
				strcpy(data+3,DspCHeader->description);
				break;
			case PI_S_MODNAME:
				{
				winampDSPModule * DSPModule = DspCHeader->getModule(WhichMod-1);

				if (!DSPModule)
				{
					FreeLibrary(hInfoPlugin);
					RETURNERROR(BAD_PLUGIN);
				}
				strcpy(data,"OK ");
				strcpy(data+3,DSPModule->description);
				}
				break;
			case PI_S_MODCOUNT:
				{
				int count = 0;
				while(DspCHeader->getModule(count))
					count++;
		
				strcpy(data,"OK ");
				itoa(count,data+3,10);
				}
				break;
			}
			break;
			}
		}
		FreeLibrary(hInfoPlugin);
		return 3;
	}
	else if (!strnicmp(data,"load ",5))
	{
		char *PluginFile = strchr(Space+1,' ');
		if (PluginFile == NULL)
			RETURNERROR(SYNTAX);
		PluginFile++;
		// What type of plugin are we loading?
		if (!strnicmp(Space+1,"input ",6))
		{
			// Input plugin
			PLUGINLIST * NewPlugin;
			switch((int)(NewPlugin = LoadInputPlugin(PluginFile)))
			{
			case 0:
				RETURNERROR(BAD_PLUGIN);
			case PE_HAVE:
				RETURNERROR(HAVE_PLUGIN);
			case PE_VERSION:
				RETURNERROR(PLUGIN_VERSION);
			}
			strcpy(data,"OK ");
			strcpy(data+3,NewPlugin->Plugin_Info->description);
			return 3;
		}
		else if (!strnicmp(Space+1,"output ",7))
		{
			if ((wcSpawn.code == WC_OUTPUT_CONFIG) || (wcSpawn.code == WC_OUTPUT_ABOUT))
				RETURNERROR(USING_WINDOW);

			// Output plugin
			Out_Module * OutPlugin = LoadOutputPlugin(PluginFile);
			switch((int)OutPlugin)
			{
			case 0:
				RETURNERROR(BAD_PLUGIN);
			case PE_VERSION:
				RETURNERROR(PLUGIN_VERSION);
			}
			strcpy(data,"OK ");
			strcpy(data+3,OutPlugin->description);
			return 3;
		}
		else if (!strnicmp(Space+1,"dsp ",4))
		{
			if (wcSpawn.code == WC_DSP_CONFIG)
				RETURNERROR(USING_WINDOW);

			// Get number
			char * NumWhich = strchr(Space+1,' ')+1;
			char * FName = strchr(NumWhich,' ');
			if (!FName)
				RETURNERROR(SYNTAX);
			FName++;
			switch(LoadDSPPlugin(FName,atoi(NumWhich)-1))
			{
			case 0:
				RETURNERROR(BAD_PLUGIN);
			case PE_HAVE:
				RETURNERROR(HAVE_PLUGIN);
			case PE_VERSION:
				RETURNERROR(PLUGIN_VERSION);
			}
			strcpy(data,"OK ");
			strcpy(data+3,lpDSPModule->description);
			return 3;
		}
	}
	else if (!stricmp(data,"unload dsp"))
	{
		if (wcSpawn.code == WC_DSP_CONFIG)
			RETURNERROR(USING_WINDOW);
		UnloadDSPPlugin();
		strcpy(data,"OK unloaded");
		return 3;
	}
	else if (!strnicmp(data,"about ",6))
	{
		// About box
		if (!stricmp(Space+1,"output"))
		{
			if (!OutPluginInfo)
				RETURNERROR(NO_PLUGIN);
			if (SendSpawnCmd(WC_OUTPUT_ABOUT,OutPluginInfo,NULL) != WC_NOTHING)
				RETURNERROR(USING_WINDOW);
			
			strcpy(data,"OK about");
			return 3;
		}
		else
		{
			PLUGINLIST * Which = PickNumberedInputPlugin(Space+1);
			if (!Which)
				RETURNERROR(NO_PLUGIN);
			if (SendSpawnCmd(WC_INPUT_ABOUT,Which->Plugin_Info,NULL) != WC_NOTHING)
				RETURNERROR(USING_WINDOW);
			strcpy(data,"OK about");
			return 3;
		}
	}
	else if (!strnicmp(data,"config ",7))
	{
		// Config box
		if (!stricmp(Space+1,"output"))
		{
			if (!OutPluginInfo)
				RETURNERROR(NO_PLUGIN);
			if (SendSpawnCmd(WC_OUTPUT_CONFIG,OutPluginInfo,NULL) != WC_NOTHING)
				RETURNERROR(USING_WINDOW);
			strcpy(data,"OK config");
			return 3;
		}
		else if (!stricmp(Space+1,"dsp"))
		{
			if (!lpDSPModule)
				RETURNERROR(NO_PLUGIN);
			if (SendSpawnCmd(WC_DSP_CONFIG,lpDSPModule,NULL) != WC_NOTHING)
				RETURNERROR(USING_WINDOW);
			strcpy(data,"OK config");
			return 3;
		}
		else
		{
			PLUGINLIST * Which = PickNumberedInputPlugin(Space+1);
			if (!Which)
				RETURNERROR(NO_PLUGIN);
			if (SendSpawnCmd(WC_INPUT_CONFIG,Which->Plugin_Info,NULL) != WC_NOTHING)
				RETURNERROR(USING_WINDOW);
			strcpy(data,"OK config");
			return 3;
		}
	}
	else if (!strnicmp(data,"description ",12))
	{
		// Retrieve description
		if (!stricmp(Space+1,"output"))
		{
			if (!OutPluginInfo)
				RETURNERROR(NO_PLUGIN);
			strcpy(data,"OK ");
			strcpy(data+3,OutPluginInfo->description);
			return 3;
		}
		else if (!stricmp(Space+1,"dsp"))
		{
			if (!lpDSPHeader)
				RETURNERROR(NO_PLUGIN);

			strcpy(data,"OK ");
			strcpy(data+3,lpDSPHeader->description);
			return 3;
		}
		else if (!stricmp(Space+1,"dspmod"))
		{
			if (!lpDSPModule)
				RETURNERROR(NO_PLUGIN);

			strcpy(data,"OK ");
			strcpy(data+3,lpDSPModule->description);
			return 3;
		}
		else
		{
			PLUGINLIST * Which = PickNumberedInputPlugin(Space+1);
			if (!Which)
				RETURNERROR(NO_PLUGIN);
			strcpy(data,"OK ");
			strcpy(data+3,Which->Plugin_Info->description);
			return 3;
		}
	}
	else if (!strnicmp(data,"typecount ",10))
	{
		PLUGINLIST *Which = PickNumberedInputPlugin(Space+1);
		if (!Which)
			RETURNERROR(NO_PLUGIN);

		// Scan the type list
		char *TypeList = Which->Plugin_Info->FileExtensions;
		int Count = 0;
		while(*TypeList)
		{
			Count++;
			TypeList += strlen(TypeList)+1;
			TypeList += strlen(TypeList)+1;
		}

		strcpy(data,"OK ");
		itoa(Count,data+3,10);
		return 3;
	}
	else if (!strnicmp(data,"typelist ",9))
	{
		// Get a list of types -- takes 2 numbers
		PLUGINLIST *Which = PickNumberedInputPlugin(Space+1);

		char *TypeNum = strchr(Space+1,' ');
		if (TypeNum == NULL)
			RETURNERROR(SYNTAX);

		TypeNum++;
		int WantType = atoi(TypeNum);
		if (WantType < 1)
			RETURNERROR(EXT_OOB);

		char *TypeList = Which->Plugin_Info->FileExtensions;
		int Count = 1;
		while(*TypeList && (Count < WantType))
		{
			Count++;
			TypeList += strlen(TypeList)+1;
			TypeList += strlen(TypeList)+1;
		}
		if (!*TypeList)
			RETURNERROR(EXT_OOB);
		strcpy(data,"OK ");
		strcpy(data+3,TypeList);
		return 3;
	}
	else if (!strnicmp(data,"typedesc ",9))
	{
		// Get a description of types -- takes 2 numbers
		PLUGINLIST *Which = PickNumberedInputPlugin(Space+1);

		char *TypeNum = strchr(Space+1,' ');
		if (TypeNum == NULL)
			RETURNERROR(SYNTAX);

		TypeNum++;
		int WantType = atoi(TypeNum);
		if (WantType < 1)
			RETURNERROR(EXT_OOB);

		char *TypeList = Which->Plugin_Info->FileExtensions;
		int Count = 1;
		while(*TypeList && (Count < WantType))
		{
			Count++;
			TypeList += strlen(TypeList)+1;
			TypeList += strlen(TypeList)+1;
		}
		if (!*TypeList)
			RETURNERROR(EXT_OOB);
		TypeList += strlen(TypeList)+1;
		strcpy(data,"OK ");
		strcpy(data+3,TypeList);
		return 3;
	}
	else if (!strnicmp(data,"alltypes ",9))
	{
		// Returns all the types of a specific plugin
		PLUGINLIST *Which = PickNumberedInputPlugin(Space+1);
		if (!Which)
			RETURNERROR(NO_PLUGIN);
		strcpy(data,"OK ");
		char *TypeList = Which->Plugin_Info->FileExtensions;
		while(*TypeList)
		{
			if (*(data+3))
				strcat(data+3,";");
			strcat(data+3,TypeList);
			TypeList += strlen(TypeList)+1;
			TypeList += strlen(TypeList)+1;
		}
		return 3;
	}
	else if (!stricmp(data,"alltypes"))
	{
		// Returns all the types of all plugins
		PLUGINLIST *Current = First;
		strcpy(data,"OK ");
		while(Current)
		{
			char *TypeList = Current->Plugin_Info->FileExtensions;
			while(*TypeList)
			{
				if (*(data+3))
					strcat(data+3,";");
				strcat(data+3,TypeList);
				TypeList += strlen(TypeList)+1;
				TypeList += strlen(TypeList)+1;
			}
			Current = Current->Next;
		}
		return 3;
	}
	RETURNERROR(SYNTAX);
}

// For the time being, uses only the first loaded plugin
int FAR PASCAL Queue(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	StayLoaded(mWnd);

	char *Space = strchr(data,' ');
	PLAYQUEUE * Target=NULL;
	if (!strnicmp(data,"add ",4))
	{
		char * FileName = data + 4;
		if (strlen(FileName) > MAX_PATH)
			RETURNERROR(LONG_NAME);
		if (!InsertPlayQueue(FileName,PQEnd))
			RETURNERROR(UNKNOWN);
		strcpy(data,"OK added");
		return 3;

	}
	else if (!stricmp(data,"count"))
	{
		strcpy(data,"OK ");
		itoa(CountPlayQueue(),data+3,10);
		return 3;
	}
	else if (!strnicmp(data,"insert ",7))
	{
		switch(SelectQueueItem(Space+1,(LPVOID *)&Target))
		{
		case QE_INVALID:
			RETURNERROR(SYNTAX);
		case QE_OOB:
		case QE_OK:
			char * FileName = strchr(Space+1,' ');
			if (FileName == NULL)
				RETURNERROR(SYNTAX);
			FileName++;
			if (Target != NULL)
				Target = Target->Prev;
			if (!InsertPlayQueue(FileName,Target))
				RETURNERROR(UNKNOWN);
			strcpy(data,"OK added");
			return 3;
		}
	}
	else if (!strnicmp(data,"remove ",7))
	{
		switch(SelectQueueItem(Space+1,(LPVOID *)&Target))
		{
		case QE_OK:
			RemovePlayQueue(Target);
			strcpy(data,"OK removed");
			return 3;
		case QE_OOB:
			RETURNERROR(QUEUE_OOB);
		case QE_INVALID:
			RETURNERROR(SYNTAX);
		}
	}
	else if (!stricmp(data,"clear"))
	{
		if (PQStart == NULL)
			RETURNERROR(QUEUE_EMPTY);
		ClearPlayQueue();
		strcpy(data,"OK cleared");
		return 3;
	}
	else if (!strnicmp(data,"looping ",8))
	{
		if (!stricmp(Space+1,"on"))
			Looping = true;
		else if (!stricmp(Space+1,"off"))
			Looping = false;
		else if (stricmp(Space+1,"get"))
			RETURNERROR(SYNTAX)
		strcpy(data,"OK ");
		strcpy(data+3,(Looping ? "on" : "off"));
		return 3;
	}
	else if (!strnicmp(data,"shuffle ",8))
	{
		if (!stricmp(Space+1,"now"))
		{
			ShufflePlayQueue();
			strcpy(data,"OK shuffled");
			return 3;
		}
		else if (!stricmp(Space+1,"auto-on"))
			AutoShuffle = true;
		else if (!stricmp(Space+1,"auto-off"))
			AutoShuffle = false;
		else if (stricmp(Space+1,"auto?"))
			RETURNERROR(SYNTAX);

		strcpy(data,"OK ");
		strcpy(data+3,(AutoShuffle ? "on" : "off"));
		return 3;
	}
	// Now for the info retrieval functions
	else if (!strnicmp(data,"filename ",9))
	{
		switch(SelectQueueItem(Space + 1,(LPVOID *)&Target))
		{
		case QE_OK:
			strcpy(data,"OK ");
			strcpy(data+3,Target->FileName);
			return 3;
		case QE_INVALID:
			RETURNERROR(SYNTAX);
		case QE_OOB:
			RETURNERROR(QUEUE_OOB);
		}
	}
	else if (!strnicmp(data,"title ",6))
	{
		switch(SelectQueueItem(Space + 1,(LPVOID *)&Target))
		{
		case QE_OK:
		{
			if ((Target == PQCurrent) && (MusicPlaying))
				MusicPlaying->Plugin_Info->GetFileInfo("",Target->TitleCache,
					&(Target->SizeCache));
			strcpy(data,"OK ");
			strcpy(data+3,Target->TitleCache);
			return 3;
		}
		case QE_INVALID:
			RETURNERROR(SYNTAX);
		case QE_OOB:
			RETURNERROR(QUEUE_OOB);
		}
	}
	else if (!strnicmp(data,"length ",7))
	{
		switch(SelectQueueItem(Space + 1,(LPVOID *)&Target))
		{
		case QE_OK:
		{
			if ((Target == PQCurrent) && (MusicPlaying))
				MusicPlaying->Plugin_Info->GetFileInfo("",Target->TitleCache,
					&(Target->SizeCache));
			strcpy(data,"OK ");
			itoa(Target->SizeCache,data+3,10);
			return 3;
		}
		case QE_INVALID:
			RETURNERROR(SYNTAX);
		case QE_OOB:
			RETURNERROR(QUEUE_OOB);
		}
	}
	else if (!strnicmp(data,"setcurrent ",11))
	{
		switch(SelectQueueItem(Space + 1,(LPVOID *)&Target))
		{
		case QE_OK:
		{
			BOOL MusicWasPlaying = false;
			BOOL WasPaused = false;
			if (MusicPlaying)
			{
				WasPaused = (MusicPlaying->Plugin_Info->IsPaused() != 0);
				MusicWasPlaying = true;
			}
			StopMusic();

			PQCurrent = Target;
			if (MusicWasPlaying)
			{
				PlayMusic(PQCurrent->FileName);
				PauseMusic(WasPaused);
			}
			strcpy(data,"OK set");
			return 3;
		}
		case QE_OOB:
			RETURNERROR(QUEUE_OOB);
		case QE_INVALID:
			RETURNERROR(SYNTAX);
		}

	}
	else if (!stricmp(data,"getcurrent"))
	{
		// Return a -1 if we're not playing from the queue
		if (PQCurrent == NULL)
		{
			strcpy(data,"OK -1");
			return 3;
		}

		// Otherwise, let's find out
		PLAYQUEUE *ScanCurrent = PQStart;
		int WhichOne = 1;

		while(ScanCurrent && (ScanCurrent != PQCurrent))
		{
			WhichOne++;
			ScanCurrent = ScanCurrent->Next;
		}
		strcpy(data,"OK ");
		itoa(WhichOne,data+3,10);
		return 3;
	}
	// Playlist functions (saving/loading)
	else if (!strnicmp(data,"saveM3U ",8))
	{
		// Save our playlist to an M3U file -- be warned that this will
		// overwrite whatever is already there
		HANDLE M3UList = CreateFile(Space+1,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
			FILE_ATTRIBUTE_ARCHIVE,0);
		if (!M3UList)
			RETURNERROR(IO);

		// First, the M3U header...
		if (!WriteLine(M3UList,"#EXTM3U"))
		{			
			CloseHandle(M3UList);
			RETURNERROR(IO);
		}

		// Now go through and build the strings and such
		PLAYQUEUE * PQScan = PQStart;
		while(PQScan)
		{
			char ExtInf[TITLESIZE+30];
			strcpy(ExtInf,"#EXTINF:");
			itoa(PQScan->SizeCache / 1000,ExtInf+8,10);
			strcat(ExtInf,",");
			strcat(ExtInf,PQScan->TitleCache);
			if (!WriteLine(M3UList,ExtInf) ||
				!WriteLine(M3UList,PQScan->FileName))
			{			
				CloseHandle(M3UList);
				RETURNERROR(IO);
			}
			PQScan = PQScan->Next;
		}
		CloseHandle(M3UList);
		strcpy(data,"OK written");
		return 3;
	}
	else if (!strnicmp(data,"loadM3U ",8))
	{
		// Load our playlist from an M3U file
		// You lose the queue either way
		ClearPlayQueue();

		HANDLE M3UList = CreateFile(Space+1,GENERIC_READ,FILE_SHARE_READ,NULL,
			OPEN_EXISTING,0,0);
		if (!M3UList)
			RETURNERROR(IO);
		char Line[TITLESIZE+30];

		int Err = ReadLine(M3UList,Line,TITLESIZE+30,NULL);
		if(Err == -1)
			RETURNERROR(IO);
		if(Err || stricmp(Line,"#EXTM3U"))
		{
			CloseHandle(M3UList);
			RETURNERROR(INVALID_FORMAT);
		}

		char songTitle[TITLESIZE];
		int songLen = 0;
		BOOL ReadingFileName = false;
		while(SetFilePointer(M3UList,0,0,FILE_CURRENT) < GetFileSize(M3UList,NULL))
		{
			Err = ReadLine(M3UList,Line,TITLESIZE+30,NULL);
			if(Err == -1)
			{
				CloseHandle(M3UList);
				RETURNERROR(IO);
			}
			if(Err)
				while(Err = ReadLine(M3UList,NULL,100,NULL))
					if(Err == -1)
					{
						CloseHandle(M3UList);
						ClearPlayQueue();
						RETURNERROR(IO);
					}
			if (Line[0] == '#')
			{
				char *TitlePos = strchr(Line,',');
				if (ReadingFileName || !TitlePos || strnicmp(Line,"#EXTINF:",8))
				{
					CloseHandle(M3UList);
					ClearPlayQueue();
					RETURNERROR(INVALID_FORMAT);
				}
				// Reading info portion
				ReadingFileName = true;
				// Grab file length
				char *LengthPos = strchr(Line,':')+1;
				songLen = atoi(LengthPos) * 1000;
				TitlePos++;
				strcpy(songTitle,TitlePos);
			}
			else
			{
				// Reading in a filename
				if (!ReadingFileName)
				{
					songLen = -1000;
					strcpy(songTitle,Line);
				}
				ReadingFileName = false;
				InsertPrecachedQueue(Line,songTitle,songLen,PQEnd);
			}
		}
		CloseHandle(M3UList);
		if (ReadingFileName)
		{
			ClearPlayQueue();
			RETURNERROR(INVALID_FORMAT);
		}
		strcpy(data,"OK loaded");
		return 3;
	}
	RETURNERROR(SYNTAX);
}

// Works like the information retrieval queue commands, but on filenames instead
int FAR PASCAL Info(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	char *Space = strchr(data,' ');
	if (!strnicmp(data,"title ",6))
	{
		PLUGINLIST *InPlugin = SelectInputPlugin(Space+1,true);		
		if (!InPlugin)
			RETURNERROR(NO_PLUGIN);
		int tmp;
		char Title[TITLESIZE];
		Title[0] = 0;
		InPlugin->Plugin_Info->GetFileInfo(Space+1,Title,&tmp);
		strcpy(data,"OK ");
		strcpy(data+3,Title);
		return 3;
	}
	else if (!strnicmp(data,"length ",7))
	{
		PLUGINLIST *InPlugin = SelectInputPlugin(Space+1,true);
		if (!InPlugin)
			RETURNERROR(NO_PLUGIN);
		int Length = 0;
		char Title[TITLESIZE];
		InPlugin->Plugin_Info->GetFileInfo(Space+1,Title,&Length);
		strcpy(data,"OK ");
		itoa(Length,data+3,10);
		return 3;
	}
	else if (!strnicmp(data,"info ",5))
	{
		PLUGINLIST *InPlugin = SelectInputPlugin(Space+1,true);
		if (!InPlugin)
			RETURNERROR(NO_PLUGIN);
		if (SendSpawnCmd(WC_INPUT_INFO,InPlugin->Plugin_Info,Space+1) != WC_NOTHING)
			RETURNERROR(USING_WINDOW);
		strcpy(data,"OK infobox");
		return 3;
	}
	RETURNERROR(SYNTAX);
}

int FAR PASCAL Play(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	char * PlayFile;
	BOOL UsingQueue = !stricmp(data,"<QUEUE>");
	if (!UsingQueue)
	{
		PQCurrent = NULL;
		PlayFile = data;
	}
	else
	{
		if (PQStart == NULL)
			RETURNERROR(QUEUE_EMPTY);
		if (PQCurrent == NULL)
			PlayFile = PQStart->FileName;
		else
			PlayFile = PQCurrent->FileName;
	}
	switch(PlayMusic(PlayFile))
	{
		case 0:
			if (!UsingQueue)
				PQCurrent = NULL;
			else
			{
				if (PQCurrent == NULL)
					PQCurrent = PQStart;
				MusicPlaying->Plugin_Info->GetFileInfo("",PQCurrent->TitleCache,&(PQCurrent->SizeCache));
			}
			strcpy(data,"OK playing");
			return 3;
		case -1:
			RETURNERROR(BAD_FILE);
		case -2:
			RETURNERROR(NO_PLUGIN);
		case -3:
			RETURNERROR(NO_OUT);
		case -4:
			RETURNERROR(PLAYING);
		case -5:
			RETURNERROR(UNKNOWN);
	}
	return 1;
}

int FAR PASCAL Stop(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	if (StopMusic())
	{
		strcpy(data,"OK stopped");
		return 3;
	}
	RETURNERROR(NOT_PLAYING);
}

int FAR PASCAL Misc(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	char *Space = strchr(data,' ');
	if (!stricmp(data,"getvolume"))
	{
		strcpy(data,"OK ");
		itoa(soundVolume,data+3,10);
		return 3;
	}
	else if (!stricmp(data,"getpan"))
	{
		strcpy(data,"OK ");
		itoa(soundPan,data+3,10);
		return 3;
	}
	else if (!strnicmp(data,"setvolume ",10))
	{
		soundVolume = atoi(Space+1);
		if (MusicPlaying)
			MusicPlaying->Plugin_Info->SetVolume(soundVolume);
		strcpy(data,"OK set");
		return 3;
	}
	else if (!strnicmp(data,"setpan ",7))
	{
		soundPan = atoi(Space+1);
		if (MusicPlaying)
			MusicPlaying->Plugin_Info->SetPan(soundPan);
		strcpy(data,"OK set");
		return 3;
	}
	else if (!strnicmp(data,"equalizer ",10))
	{
		Space++;
		// Equalizer-specific stuff

		if (!stricmp(Space,"enable"))
		{
			Equalizer.Enabled = 1;
			ResetEqualizer();
			strcpy(data,"OK enabled");
			return 3;
		}
		else if (!stricmp(Space,"disable"))
		{
			Equalizer.Enabled = 0;
			ResetEqualizer();
			strcpy(data,"OK disabled");
			return 3;
		}
		else if (!stricmp(Space,"enabled?"))
		{
			strcpy(data,"OK ");
			strcpy(data + 3,(Equalizer.Enabled ? "yes" : "no"));
			return 3;
		}
		else if (!strnicmp(Space,"preamp-set ",11))
		{
			int PVal = atoi(strchr(Space,' ')+1);
			if (PVal < 0)
				PVal = 0;
			else if (PVal > 63)
				PVal = 63;
			Equalizer.PreAmplification = PVal;
			ResetEqualizer();
			strcpy(data,"OK set");
			return 3;
		}
		else if (!stricmp(Space,"preamp-get"))
		{
			strcpy(data,"OK ");
			itoa(Equalizer.PreAmplification,data+3,10);
			return 3;
		}
		else if (!stricmp(Space,"get"))
		{
			// Gets the 10 equalizer data settings
			strcpy(data,"OK");
			for(int x=0;x<10;x++)
			{
				char Number[6];
				itoa(Equalizer.Data[x],Number,10);
				strcat(data," ");
				strcat(data,Number);
			}
			return 3;
		}
		else if (!strnicmp(Space,"set ",4))
		{
			// Sets the 10 equalizer data settings -- all 10 must be set or nothing
			// is changed
			
			char * Space2 = strchr(Space,' ');
			char TempData[10];
			int x;
			for(x=0;x<10;x++)
			{
				if (Space2 == NULL)
					RETURNERROR(SYNTAX);
				Space2++;

				TempData[x] = (char)atoi(Space2);
				if (TempData[x] < 0)
					TempData[x] = 0;
				else if (TempData[x] > 63)
					TempData[x] = 63;

				Space2 = strchr(Space2,' ');
			}
			if (Space2 != NULL)
				RETURNERROR(SYNTAX);
			for(x=0;x<10;x++)
				Equalizer.Data[x] = TempData[x];
			ResetEqualizer();
			strcpy(data,"OK set");
			return 3;
		}
	}
	else if (!stricmp(data,"box"))
	{
		switch(wcSpawn.code)
		{
		case WC_NOTHING:
			strcpy(data,"OK none");
			break;
		case WC_INPUT_INFO:
			strcpy(data,"OK info ");
			strcpy(data+8,wcSpawn.FileName);
			break;
		case WC_INPUT_ABOUT:
		{
			strcpy(data,"OK about input ");
			int pNum = FindInputPluginNumber((In_Module *)wcSpawn.lpPlugin);
			if (pNum == NULL)
				RETURNERROR(UNKNOWN);
			itoa(pNum,data+15,10);
		}
			break;
		case WC_INPUT_CONFIG:
		{
			strcpy(data,"OK config input ");
			int pNum = FindInputPluginNumber((In_Module *)wcSpawn.lpPlugin);
			if (pNum == NULL)
				RETURNERROR(UNKNOWN);
			itoa(pNum,data+16,10);
		}
			break;
		case WC_OUTPUT_ABOUT:
			strcpy(data,"OK about output");
			break;
		case WC_OUTPUT_CONFIG:
			strcpy(data,"OK config output");
			break;
		case WC_DSP_CONFIG:
			strcpy(data,"OK config dsp");
			break;
		default:
			RETURNERROR(UNKNOWN);
		}
		return 3;
	}
	else if (!strnicmp(data,"notify ",7))
	{
		// Set notify method
		if (!stricmp(Space+1,"none"))
			NotifyMethod = WN_NONE;
		else if (!stricmp(Space+1,"wave"))
			NotifyMethod = WN_WAVE;
		else if (!stricmp(Space+1,"midi"))
			NotifyMethod = WN_MIDI;
		else if (stricmp(Space+1,"mode?"))
			RETURNERROR(SYNTAX);

		strcpy(data,"OK ");
		switch(NotifyMethod)
		{
		case WN_NONE:
			strcpy(data+3,"none");
			break;
		case WN_WAVE:
			strcpy(data+3,"wave");
			break;
		case WN_MIDI:
			strcpy(data+3,"midi");
			break;
		default:
			strcpy(data+3,"unknown");
			break;
		}
		return 3;
	}

	RETURNERROR(SYNTAX);
}

int FAR PASCAL FlushAllPlugins(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	StayLoaded(mWnd);

	if (hFakeAmpWindow)
		DestroyWindow(hFakeAmpWindow);
	
	FreeLibrary(Self);
	return 1;
}

int FAR PASCAL DLLInfo(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
{
	strcpy(data,AUTHINFO);
	return 3;
}

#ifdef __cplusplus
}
#endif
