/*  Pcsx2 - Pc Ps2 Emulator
 *  Copyright (C) 2002-2009  Pcsx2 Team
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include "PrecompiledHeader.h"

#include "Common.h"
#include "PsxCommon.h"
#include "GS.h"

_GSinit            GSinit;
_GSopen            GSopen;
_GSclose           GSclose;
_GSshutdown        GSshutdown;
_GSvsync           GSvsync;
_GSgifTransfer1    GSgifTransfer1;
_GSgifTransfer2    GSgifTransfer2;
_GSgifTransfer3    GSgifTransfer3;
_GSgetLastTag      GSgetLastTag;
_GSgifSoftReset    GSgifSoftReset;
_GSreadFIFO        GSreadFIFO;
_GSreadFIFO2       GSreadFIFO2;

_GSkeyEvent        GSkeyEvent;
_GSchangeSaveState GSchangeSaveState;
_GSmakeSnapshot	   GSmakeSnapshot;
_GSmakeSnapshot2   GSmakeSnapshot2;
_GSirqCallback 	   GSirqCallback;
_GSprintf      	   GSprintf;
_GSsetBaseMem 	   GSsetBaseMem;
_GSsetGameCRC		GSsetGameCRC;
_GSsetFrameSkip	   GSsetFrameSkip;
_GSsetupRecording GSsetupRecording;
_GSreset		   GSreset;
_GSwriteCSR		   GSwriteCSR;
_GSgetDriverInfo   GSgetDriverInfo;
#ifdef _WIN32
_GSsetWindowInfo   GSsetWindowInfo;
#endif
_GSfreeze          GSfreeze;
_GSconfigure       GSconfigure;
_GStest            GStest;
_GSabout           GSabout;

// PAD1
_PADinit           PAD1init;
_PADopen           PAD1open;
_PADclose          PAD1close;
_PADshutdown       PAD1shutdown;
_PADkeyEvent       PAD1keyEvent;
_PADstartPoll      PAD1startPoll;
_PADpoll           PAD1poll;
_PADquery          PAD1query;
_PADupdate         PAD1update;

_PADgsDriverInfo   PAD1gsDriverInfo;
_PADconfigure      PAD1configure;
_PADtest           PAD1test;
_PADabout          PAD1about;

// PAD2
_PADinit           PAD2init;
_PADopen           PAD2open;
_PADclose          PAD2close;
_PADshutdown       PAD2shutdown;
_PADkeyEvent       PAD2keyEvent;
_PADstartPoll      PAD2startPoll;
_PADpoll           PAD2poll;
_PADquery          PAD2query;
_PADupdate         PAD2update;

_PADgsDriverInfo   PAD2gsDriverInfo;
_PADconfigure      PAD2configure;
_PADtest           PAD2test;
_PADabout          PAD2about;

// SIO[2]
_SIOinit           SIOinit[2][9];
_SIOopen           SIOopen[2][9];
_SIOclose          SIOclose[2][9];
_SIOshutdown       SIOshutdown[2][9];
_SIOstartPoll      SIOstartPoll[2][9];
_SIOpoll           SIOpoll[2][9];
_SIOquery          SIOquery[2][9];

_SIOconfigure      SIOconfigure[2][9];
_SIOtest           SIOtest[2][9];
_SIOabout          SIOabout[2][9];

// SPU2
_SPU2init          SPU2init;
_SPU2open          SPU2open;
_SPU2close         SPU2close;
_SPU2shutdown      SPU2shutdown;
_SPU2write         SPU2write;
_SPU2read          SPU2read;
_SPU2readDMA4Mem   SPU2readDMA4Mem;
_SPU2writeDMA4Mem  SPU2writeDMA4Mem;
_SPU2interruptDMA4 SPU2interruptDMA4;
_SPU2readDMA7Mem   SPU2readDMA7Mem;
_SPU2writeDMA7Mem  SPU2writeDMA7Mem;
_SPU2setDMABaseAddr SPU2setDMABaseAddr;
_SPU2interruptDMA7 SPU2interruptDMA7;
_SPU2ReadMemAddr   SPU2ReadMemAddr;
_SPU2setupRecording SPU2setupRecording;
_SPU2WriteMemAddr   SPU2WriteMemAddr;
_SPU2irqCallback   SPU2irqCallback;

_SPU2setClockPtr   SPU2setClockPtr;
_SPU2setTimeStretcher SPU2setTimeStretcher;

_SPU2async         SPU2async;
_SPU2freeze        SPU2freeze;
_SPU2configure     SPU2configure;
_SPU2test          SPU2test;
_SPU2about         SPU2about;

// CDVD
_CDVDinit          CDVDinit;
_CDVDopen          CDVDopen;
_CDVDclose         CDVDclose;
_CDVDshutdown      CDVDshutdown;
_CDVDreadTrack     CDVDreadTrack;
_CDVDgetBuffer     CDVDgetBuffer;
_CDVDreadSubQ      CDVDreadSubQ;
_CDVDgetTN         CDVDgetTN;
_CDVDgetTD         CDVDgetTD;
_CDVDgetTOC        CDVDgetTOC;
_CDVDgetDiskType   CDVDgetDiskType;
_CDVDgetTrayStatus CDVDgetTrayStatus;
_CDVDctrlTrayOpen  CDVDctrlTrayOpen;
_CDVDctrlTrayClose CDVDctrlTrayClose;

_CDVDconfigure     CDVDconfigure;
_CDVDtest          CDVDtest;
_CDVDabout         CDVDabout;
_CDVDnewDiskCB     CDVDnewDiskCB;

// DEV9
_DEV9init          DEV9init;
_DEV9open          DEV9open;
_DEV9close         DEV9close;
_DEV9shutdown      DEV9shutdown;
_DEV9read8         DEV9read8;
_DEV9read16        DEV9read16;
_DEV9read32        DEV9read32;
_DEV9write8        DEV9write8;
_DEV9write16       DEV9write16;
_DEV9write32       DEV9write32;
_DEV9readDMA8Mem   DEV9readDMA8Mem;
_DEV9writeDMA8Mem  DEV9writeDMA8Mem;
_DEV9irqCallback   DEV9irqCallback;
_DEV9irqHandler    DEV9irqHandler;

_DEV9configure     DEV9configure;
_DEV9freeze        DEV9freeze;
_DEV9test          DEV9test;
_DEV9about         DEV9about;

// USB
_USBinit           USBinit;
_USBopen           USBopen;
_USBclose          USBclose;
_USBshutdown       USBshutdown;
_USBread8          USBread8;
_USBread16         USBread16;
_USBread32         USBread32;
_USBwrite8         USBwrite8;
_USBwrite16        USBwrite16;
_USBwrite32        USBwrite32;
_USBasync          USBasync;

_USBirqCallback    USBirqCallback;
_USBirqHandler     USBirqHandler;
_USBsetRAM         USBsetRAM;

_USBconfigure      USBconfigure;
_USBfreeze         USBfreeze;
_USBtest           USBtest;
_USBabout          USBabout;

// FW
_FWinit            FWinit;
_FWopen            FWopen;
_FWclose           FWclose;
_FWshutdown        FWshutdown;
_FWread32          FWread32;
_FWwrite32         FWwrite32;
_FWirqCallback     FWirqCallback;

_FWconfigure       FWconfigure;
_FWfreeze          FWfreeze;
_FWtest            FWtest;
_FWabout           FWabout;


DEV9handler dev9Handler;
USBhandler usbHandler;

#define Sfy(x) #x
#define Strfy(x) Sfy(x)
#define MapSymbolVarType(var,type,name) var = (type)SysLoadSym(drv,Strfy(name))
#define MapSymbolVar(var,name) MapSymbolVarType(var,_##name,name)
#define MapSymbolVar_Fallback(var,name,fallback) if((MapSymbolVar(var,name))==NULL) var = fallback
#define MapSymbolVar_Error(var,name) if((MapSymbolVar(var,name))==NULL) \
{ \
	const char* errString = SysLibError(); \
	Msgbox::Alert("%s: Error loading %hs: %s", params &filename, #name, errString); \
	return -1; \
} 

#define MapSymbol(name) MapSymbolVar(name,name)
#define MapSymbol_Fallback(name,fallback) MapSymbolVar_Fallback(name,name,fallback)
#define MapSymbol_Error(name) MapSymbolVar_Error(name,name)

// for pad1/2
#define MapSymbolPAD(var,sym,name) MapSymbolVar(var##name,sym##name)
#define MapSymbolPAD_Fallback(var,sym,name) if((MapSymbolVarType(var##name,_##sym##name,sym##name))==NULL) var##name = var##_##name
#define MapSymbolPAD_Error(var,sym,name) MapSymbolVar_Error(var##name,sym##name)

#define TestPS2Esyms(type) if(_TestPS2Esyms(drv,PS2E_LT_##type,PS2E_##type##_VERSION,filename) < 0) return -1;

int _TestPS2Esyms(void* drv, int type, int expected_version, const string& filename)
{
	_PS2EgetLibType PS2EgetLibType;
	_PS2EgetLibVersion2 PS2EgetLibVersion2;
	_PS2EgetLibName PS2EgetLibName;

	MapSymbol_Error(PS2EgetLibType);
	MapSymbol_Error(PS2EgetLibVersion2);
	MapSymbol_Error(PS2EgetLibName);

	int actual_version = ((PS2EgetLibVersion2(type) >> 16)&0xff);

	if( actual_version != expected_version) {
		Msgbox::Alert("Can't load '%hs', wrong PS2E version (%x != %x)", params &filename, actual_version, expected_version);
		return -1;
	}

	return 0;
}

//static const char *err;
//static int errval;

void *GSplugin;

void CALLBACK GS_printf(int timeout, char *fmt, ...) {
	va_list list;
	char msg[512];

	va_start(list, fmt);
	vsprintf(msg, fmt, list);
	va_end(list);

	SysPrintf(msg);
}

s32  CALLBACK GS_freeze(int mode, freezeData *data) { data->size = 0; return 0; }
void CALLBACK GS_keyEvent(keyEvent *ev) {}
void CALLBACK GS_makeSnapshot(const char *path) {}
void CALLBACK GS_irqCallback(void (*callback)()) {}
void CALLBACK GS_configure() {}
void CALLBACK GS_about() {}
s32  CALLBACK GS_test() { return 0; }

int LoadGSplugin(const string& filename)
{
	void *drv;

	GSplugin = SysLoadLibrary(filename.c_str());
	if (GSplugin == NULL) { Msgbox::Alert ("Could Not Load GS Plugin '%hs': %s", params &filename, SysLibError()); return -1; }
	drv = GSplugin;
	TestPS2Esyms(GS);
	MapSymbol_Error(GSinit);
	MapSymbol_Error(GSshutdown);
	MapSymbol_Error(GSopen);
	MapSymbol_Error(GSclose);
	MapSymbol_Error(GSgifTransfer1);
	MapSymbol_Error(GSgifTransfer2);
	MapSymbol_Error(GSgifTransfer3);
	MapSymbol_Error(GSreadFIFO);
	MapSymbol(GSgetLastTag);
	MapSymbol(GSreadFIFO2); // optional
	MapSymbol_Error(GSvsync);

	MapSymbol_Fallback(GSkeyEvent,GS_keyEvent);
	MapSymbol(GSchangeSaveState);
	MapSymbol(GSgifSoftReset);
	MapSymbol_Fallback(GSmakeSnapshot,GS_makeSnapshot);
	MapSymbol_Fallback(GSirqCallback,GS_irqCallback);
	MapSymbol_Fallback(GSprintf,GS_printf);
	MapSymbol_Error(GSsetBaseMem);
	MapSymbol(GSsetGameCRC);
	MapSymbol_Error(GSreset);
	MapSymbol_Error(GSwriteCSR);
	MapSymbol(GSmakeSnapshot2);
	MapSymbol(GSgetDriverInfo);

	MapSymbol(GSsetFrameSkip);
	MapSymbol(GSsetupRecording);

#ifdef _WIN32
	MapSymbol(GSsetWindowInfo);
#endif
	MapSymbol_Fallback(GSfreeze,GS_freeze);
	MapSymbol_Fallback(GSconfigure,GS_configure);
	MapSymbol_Fallback(GSabout,GS_about);
	MapSymbol_Fallback(GStest,GS_test);

	return 0;
}

void *PAD1plugin;

void CALLBACK PAD1_configure() {}
void CALLBACK PAD1_about() {}
s32  CALLBACK PAD1_test() { return 0; }

int LoadPAD1plugin(const string& filename) {
	void *drv;

	PAD1plugin = SysLoadLibrary(filename.c_str());
	if (PAD1plugin == NULL) { Msgbox::Alert("Could Not Load PAD1 Plugin '%hs': %s", params &filename, SysLibError()); return -1; }
	drv = PAD1plugin;
	TestPS2Esyms(PAD);
	MapSymbolPAD_Error(PAD1,PAD,init);
	MapSymbolPAD_Error(PAD1,PAD,shutdown);
	MapSymbolPAD_Error(PAD1,PAD,open);
	MapSymbolPAD_Error(PAD1,PAD,close);
	MapSymbolPAD_Error(PAD1,PAD,keyEvent);
	MapSymbolPAD_Error(PAD1,PAD,startPoll);
	MapSymbolPAD_Error(PAD1,PAD,poll);
	MapSymbolPAD_Error(PAD1,PAD,query);
	MapSymbolPAD(PAD1,PAD,update);

	MapSymbolPAD(PAD1,PAD,gsDriverInfo);
	MapSymbolPAD_Fallback(PAD1,PAD,configure);
	MapSymbolPAD_Fallback(PAD1,PAD,about);
	MapSymbolPAD_Fallback(PAD1,PAD,test);

	return 0;
}

void *PAD2plugin;

void CALLBACK PAD2_configure() {}
void CALLBACK PAD2_about() {}
s32  CALLBACK PAD2_test() { return 0; }

int LoadPAD2plugin(const string& filename) {
	void *drv;

	PAD2plugin = SysLoadLibrary(filename.c_str());
	if (PAD2plugin == NULL) { Msgbox::Alert("Could Not Load PAD2 Plugin '%hs': %s", params &filename, SysLibError()); return -1; }
	drv = PAD2plugin;
	TestPS2Esyms(PAD);
	MapSymbolPAD_Error(PAD2,PAD,init);
	MapSymbolPAD_Error(PAD2,PAD,shutdown);
	MapSymbolPAD_Error(PAD2,PAD,open);
	MapSymbolPAD_Error(PAD2,PAD,close);
	MapSymbolPAD_Error(PAD2,PAD,keyEvent);
	MapSymbolPAD_Error(PAD2,PAD,startPoll);
	MapSymbolPAD_Error(PAD2,PAD,poll);
	MapSymbolPAD_Error(PAD2,PAD,query);
	MapSymbolPAD(PAD2,PAD,update);

	MapSymbolPAD(PAD2,PAD,gsDriverInfo);
	MapSymbolPAD_Fallback(PAD2,PAD,configure);
	MapSymbolPAD_Fallback(PAD2,PAD,about);
	MapSymbolPAD_Fallback(PAD2,PAD,test);

	return 0;
}

void *SPU2plugin;

s32  CALLBACK SPU2_freeze(int mode, freezeData *data) { data->size = 0; return 0; }
void CALLBACK SPU2_configure() {}
void CALLBACK SPU2_about() {}
s32  CALLBACK SPU2_test() { return 0; }

int LoadSPU2plugin(const string& filename) {
	void *drv;

	SPU2plugin = SysLoadLibrary(filename.c_str());
	if (SPU2plugin == NULL) { Msgbox::Alert("Could Not Load SPU2 Plugin '%hs': %s", params &filename, SysLibError()); return -1; }
	drv = SPU2plugin;
	TestPS2Esyms(SPU2);
	MapSymbol_Error(SPU2init);
	MapSymbol_Error(SPU2shutdown);
	MapSymbol_Error(SPU2open);
	MapSymbol_Error(SPU2close);
	MapSymbol_Error(SPU2write);
	MapSymbol_Error(SPU2read);
	MapSymbol_Error(SPU2readDMA4Mem);     
	MapSymbol_Error(SPU2writeDMA4Mem);   
	MapSymbol_Error(SPU2interruptDMA4);
	MapSymbol_Error(SPU2readDMA7Mem);     
	MapSymbol_Error(SPU2writeDMA7Mem);  
	MapSymbol_Error(SPU2interruptDMA7);
	MapSymbol(SPU2setDMABaseAddr);
	MapSymbol_Error(SPU2ReadMemAddr);
	MapSymbol_Error(SPU2WriteMemAddr);
	MapSymbol_Error(SPU2irqCallback);

	MapSymbol(SPU2setClockPtr);

	MapSymbol(SPU2setupRecording);

	MapSymbol_Fallback(SPU2freeze,SPU2_freeze);
	MapSymbol_Fallback(SPU2configure,SPU2_configure);
	MapSymbol_Fallback(SPU2about,SPU2_about);
	MapSymbol_Fallback(SPU2test,SPU2_test);
	MapSymbol(SPU2async);

	return 0;
}

void *CDVDplugin;

void CALLBACK CDVD_configure() {}
void CALLBACK CDVD_about() {}
s32  CALLBACK CDVD_test() { return 0; }

int LoadCDVDplugin(const string& filename) {
	void *drv;

	CDVDplugin = SysLoadLibrary(filename.c_str());
	if (CDVDplugin == NULL) { Msgbox::Alert("Could Not Load CDVD Plugin '%hs': %s", params &filename, SysLibError()); return -1; }
	drv = CDVDplugin;
	TestPS2Esyms(CDVD);
	MapSymbol_Error(CDVDinit);
	MapSymbol_Error(CDVDshutdown);
	MapSymbol_Error(CDVDopen);
	MapSymbol_Error(CDVDclose);
	MapSymbol_Error(CDVDreadTrack);
	MapSymbol_Error(CDVDgetBuffer);
	MapSymbol_Error(CDVDreadSubQ);
	MapSymbol_Error(CDVDgetTN);
	MapSymbol_Error(CDVDgetTD);
	MapSymbol_Error(CDVDgetTOC);
	MapSymbol_Error(CDVDgetDiskType);
	MapSymbol_Error(CDVDgetTrayStatus);
	MapSymbol_Error(CDVDctrlTrayOpen);
	MapSymbol_Error(CDVDctrlTrayClose);

	MapSymbol_Fallback(CDVDconfigure,CDVD_configure);
	MapSymbol_Fallback(CDVDabout,CDVD_about);
	MapSymbol_Fallback(CDVDtest,CDVD_test);
	MapSymbol(CDVDnewDiskCB);

	return 0;
}

void *DEV9plugin;

s32  CALLBACK DEV9_freeze(int mode, freezeData *data) { data->size = 0; return 0; }
void CALLBACK DEV9_configure() {}
void CALLBACK DEV9_about() {}
s32  CALLBACK DEV9_test() { return 0; }

int LoadDEV9plugin(const string& filename) {
	void *drv;

	DEV9plugin = SysLoadLibrary(filename.c_str());
	if (DEV9plugin == NULL) { Msgbox::Alert("Could Not Load DEV9 Plugin '%hs': %s", params &filename, SysLibError()); return -1; }
	drv = DEV9plugin;
	TestPS2Esyms(DEV9);
	MapSymbol_Error(DEV9init);
	MapSymbol_Error(DEV9shutdown);
	MapSymbol_Error(DEV9open);
	MapSymbol_Error(DEV9close);
	MapSymbol_Error(DEV9read8);
	MapSymbol_Error(DEV9read16);
	MapSymbol_Error(DEV9read32);
	MapSymbol_Error(DEV9write8);
	MapSymbol_Error(DEV9write16);
	MapSymbol_Error(DEV9write32);
	MapSymbol_Error(DEV9readDMA8Mem);
	MapSymbol_Error(DEV9writeDMA8Mem);
	MapSymbol_Error(DEV9irqCallback);
	MapSymbol_Error(DEV9irqHandler);

	MapSymbol_Fallback(DEV9freeze,DEV9_freeze);
	MapSymbol_Fallback(DEV9configure,DEV9_configure);
	MapSymbol_Fallback(DEV9about,DEV9_about);
	MapSymbol_Fallback(DEV9test,DEV9_test);

	return 0;
}

void *USBplugin;

s32  CALLBACK USB_freeze(int mode, freezeData *data) { data->size = 0; return 0; }
void CALLBACK USB_configure() {}
void CALLBACK USB_about() {}
s32 CALLBACK USB_test() { return 0; }

int LoadUSBplugin(const string& filename) {
	void *drv;

	USBplugin = SysLoadLibrary(filename.c_str());
	if (USBplugin == NULL) { Msgbox::Alert("Could Not Load USB Plugin '%hs': %s", params &filename, SysLibError()); return -1; }
	drv = USBplugin;
	TestPS2Esyms(USB);
	MapSymbol_Error(USBinit);
	MapSymbol_Error(USBshutdown);
	MapSymbol_Error(USBopen);
	MapSymbol_Error(USBclose);
	MapSymbol_Error(USBread8);
	MapSymbol_Error(USBread16);
	MapSymbol_Error(USBread32);
	MapSymbol_Error(USBwrite8);
	MapSymbol_Error(USBwrite16);
	MapSymbol_Error(USBwrite32);
	MapSymbol_Error(USBirqCallback);
	MapSymbol_Error(USBirqHandler);
	MapSymbol_Error(USBsetRAM);
	
	MapSymbol(USBasync);

	MapSymbol_Fallback(USBfreeze,USB_freeze);
	MapSymbol_Fallback(USBconfigure,USB_configure);
	MapSymbol_Fallback(USBabout,USB_about);
	MapSymbol_Fallback(USBtest,USB_test);

	return 0;
}
void *FWplugin;

s32  CALLBACK FW_freeze(int mode, freezeData *data) { data->size = 0; return 0; }
void CALLBACK FW_configure() {}
void CALLBACK FW_about() {}
s32 CALLBACK FW_test() { return 0; }

int LoadFWplugin(const string& filename) {
	void *drv;

	FWplugin = SysLoadLibrary(filename.c_str());
	if (FWplugin == NULL) { Msgbox::Alert("Could Not Load FW Plugin '%hs': %s", params &filename, SysLibError()); return -1; }
	drv = FWplugin;
	TestPS2Esyms(FW);
	MapSymbol_Error(FWinit);
	MapSymbol_Error(FWshutdown);
	MapSymbol_Error(FWopen);
	MapSymbol_Error(FWclose);
	MapSymbol_Error(FWread32);
	MapSymbol_Error(FWwrite32);
	MapSymbol_Error(FWirqCallback);

	MapSymbol_Fallback(FWfreeze,FW_freeze);
	MapSymbol_Fallback(FWconfigure,FW_configure);
	MapSymbol_Fallback(FWabout,FW_about);
	MapSymbol_Fallback(FWtest,FW_test);

	return 0;
}

struct PluginOpenStatusFlags
{
	u8	GS : 1
	,	CDVD : 1
	,	DEV9 : 1
	,	USB : 1
	,	SPU2 : 1
	,	PAD1 : 1
	,	PAD2 : 1
	,	FW : 1;

};

static PluginOpenStatusFlags OpenStatus = {0};

static bool loadp = false;
static bool initp = false;

int LoadPlugins()
{
	if( loadp ) return 0;

	string Plugin;

	Path::Combine( Plugin, Config.PluginsDir, Config.GS );
	if (LoadGSplugin(Plugin) == -1) return -1;

	Path::Combine( Plugin, Config.PluginsDir, Config.PAD1 );
	if (LoadPAD1plugin(Plugin) == -1) return -1;

	Path::Combine( Plugin, Config.PluginsDir, Config.PAD2);
	if (LoadPAD2plugin(Plugin) == -1) return -1;

	Path::Combine( Plugin, Config.PluginsDir, Config.SPU2);
	if (LoadSPU2plugin(Plugin) == -1) return -1;

	Path::Combine( Plugin, Config.PluginsDir, Config.CDVD);
	if (LoadCDVDplugin(Plugin) == -1) return -1;

	Path::Combine( Plugin, Config.PluginsDir, Config.DEV9);
	if (LoadDEV9plugin(Plugin) == -1) return -1;

	Path::Combine( Plugin, Config.PluginsDir, Config.USB);
	if (LoadUSBplugin(Plugin) == -1) return -1;

	Path::Combine( Plugin, Config.PluginsDir, Config.FW);
	if (LoadFWplugin(Plugin) == -1) return -1;

	loadp = true;

	return 0;
}


int InitPlugins()
{
	if( initp ) return 0;

	// Ensure plugins have been loaded....
	if( LoadPlugins() == -1 ) return -1;

	//if( !loadp )
	//	throw Exception::InvalidOperation( "Bad coder mojo - InitPlugins called prior to plugins having been loaded." );

#ifndef _WIN32
	chdir(MAIN_DIR);
#endif
	int ret;

	ret = GSinit();
	if (ret != 0) { Msgbox::Alert("GSinit error: %d", params ret); return -1; }
	ret = PAD1init(1);
	if (ret != 0) { Msgbox::Alert("PAD1init error: %d", params ret); return -1; }
	ret = PAD2init(2);
	if (ret != 0) { Msgbox::Alert("PAD2init error: %d", params ret); return -1; }
	ret = SPU2init();
	if (ret != 0) { Msgbox::Alert("SPU2init error: %d", params ret); return -1; }
	ret = CDVDinit();
	if (ret != 0) { Msgbox::Alert("CDVDinit error: %d", params ret); return -1; }
	ret = DEV9init();
	if (ret != 0) { Msgbox::Alert("DEV9init error: %d", params ret); return -1; }
	ret = USBinit();
	if (ret != 0) { Msgbox::Alert("USBinit error: %d", params ret); return -1; }
	ret = FWinit();
	if (ret != 0) { Msgbox::Alert("FWinit error: %d", params ret); return -1; }

	initp = true;
	return 0;
}

void ShutdownPlugins()
{
	if( !initp ) return;
	
	ClosePlugins( true );

	if( GSshutdown != NULL )
		GSshutdown();
	
	if( PAD1shutdown != NULL )
		PAD1shutdown();
	if( PAD2shutdown != NULL )
		PAD2shutdown();

	if( SPU2shutdown != NULL )
		SPU2shutdown();

	if( CDVDshutdown != NULL )
		CDVDshutdown();

	if( DEV9shutdown != NULL )
		DEV9shutdown();

	if( USBshutdown != NULL )
		USBshutdown();

	if( FWshutdown != NULL )
		FWshutdown();

	initp = false;
}

uptr pDsp;
extern void spu2DMA4Irq();
extern void spu2DMA7Irq();
extern void spu2Irq();

int OpenPlugins(const char* pTitleFilename)
{
	GSdriverInfo info;
	int ret;

	if ( !initp ) InitPlugins();
		//throw Exception::InvalidOperation( "Bad coder mojo -- OpenPlugins called prior to InitPlugins." );

#ifndef _WIN32
	// change dir so that CDVD can find its config file
	char file[255], pNewTitle[255];
	chdir(MAIN_DIR);
	chdir(Config.PluginsDir);

	if( pTitleFilename != NULL && pTitleFilename[0] != '/' ) 
	{
		// because we are changing the dir, we have to set a new title if it is a relative dir
		sprintf(pNewTitle, "%s/%s", file, pTitleFilename);
		pTitleFilename = pNewTitle;
	}
#endif

	if( !OpenStatus.CDVD )
	{
		//first we need the data
		if (CDVDnewDiskCB) CDVDnewDiskCB(cdvdNewDiskCB);

		ret = CDVDopen(pTitleFilename);

		if (ret != 0) { Msgbox::Alert("Error Opening CDVD Plugin"); goto OpenError; }
		OpenStatus.CDVD = true;
		cdvdNewDiskCB();
	}

	if( !OpenStatus.GS ) {
		ret = gsOpen();
		if (ret != 0) { Msgbox::Alert("Error Opening GS Plugin"); goto OpenError; }
		OpenStatus.GS = true;

		//then the user input
		if (GSgetDriverInfo) {
			GSgetDriverInfo(&info);
			if (PAD1gsDriverInfo) PAD1gsDriverInfo(&info);
			if (PAD2gsDriverInfo) PAD2gsDriverInfo(&info);
		}
	}

	if( !OpenStatus.PAD1 )
	{
		ret = PAD1open((void *)&pDsp);
		if (ret != 0) { Msgbox::Alert("Error Opening PAD1 Plugin"); goto OpenError; }
		OpenStatus.PAD1 = true;
	}

	if( !OpenStatus.PAD2 )
	{
		ret = PAD2open((void *)&pDsp);
		if (ret != 0) { Msgbox::Alert("Error Opening PAD2 Plugin"); goto OpenError; }
		OpenStatus.PAD2 = true;
	}

	//the sound

	if( !OpenStatus.SPU2 )
	{
		SPU2irqCallback(spu2Irq,spu2DMA4Irq,spu2DMA7Irq);
		if( SPU2setDMABaseAddr != NULL )
			SPU2setDMABaseAddr((uptr)psxM);

		if(SPU2setClockPtr != NULL)
			SPU2setClockPtr(&psxRegs.cycle);

		ret = SPU2open((void*)&pDsp);
		if (ret != 0) { Msgbox::Alert("Error Opening SPU2 Plugin"); goto OpenError; }
		OpenStatus.SPU2 = true;
	}

	//and last the dev9
	if( !OpenStatus.DEV9 )
	{
		DEV9irqCallback(dev9Irq);
		dev9Handler = DEV9irqHandler();
		ret = DEV9open(&psxRegs.pc); //((void *)&pDsp);
		if (ret != 0) { Msgbox::Alert("Error Opening DEV9 Plugin"); goto OpenError; }
		OpenStatus.DEV9 = true;
	}

	if( !OpenStatus.USB )
	{
		USBirqCallback(usbIrq);
		usbHandler = USBirqHandler();
		USBsetRAM(psxM);
		ret = USBopen((void *)&pDsp);
		if (ret != 0) { Msgbox::Alert("Error Opening USB Plugin"); goto OpenError; }
		OpenStatus.USB = true;
	}

	if( !OpenStatus.FW )
	{
		FWirqCallback(fwIrq);
		ret = FWopen((void *)&pDsp);
		if (ret != 0) { Msgbox::Alert("Error Opening FW Plugin"); goto OpenError; }
		OpenStatus.FW = true;
	}

#ifndef _WIN32
	chdir(MAIN_DIR);
#endif
	return 0;

OpenError:
	ClosePlugins( true );
#ifndef _WIN32
	chdir(MAIN_DIR);
#endif

    return -1;
}


#define CLOSE_PLUGIN( name ) \
	if( OpenStatus.name ) { \
		name##close(); \
		OpenStatus.name = false; \
	}


void ClosePlugins( bool closegs )
{
	// Close pads first since they attatch to the GS's window.
	
	CLOSE_PLUGIN( PAD1 );
	CLOSE_PLUGIN( PAD2 );

	// GS plugin is special and is not always closed during emulation pauses.
	// (that's because the GS is the most complicated plugin and to close it would
	// require we save the GS state)

	if( OpenStatus.GS )
	{
		if( closegs )
		{
			gsClose();
			OpenStatus.GS = false;
		}
		else
			mtgsWaitGS();
	}

	CLOSE_PLUGIN( CDVD );
	CLOSE_PLUGIN( DEV9 );
	CLOSE_PLUGIN( USB );
	CLOSE_PLUGIN( FW );
	CLOSE_PLUGIN( SPU2 );
	
	// More special treatment for the GS.  It needs a complete shutdown and re-init
	// or else it will tend to error out when we try to use it again.
	if( 0 ) //closegs )
	{
		GSshutdown();

		int ret = GSinit();
		if (ret != 0) { Msgbox::Alert("GSinit error: %d", params ret);  }
	}
}

void ResetPlugins()
{
	
	mtgsWaitGS();

	ShutdownPlugins();
	InitPlugins();
}

void ReleasePlugins()
{
	if (!loadp) return;

	if (GSplugin   == NULL || PAD1plugin == NULL || PAD2plugin == NULL ||
		SPU2plugin == NULL || CDVDplugin == NULL || DEV9plugin == NULL ||
		USBplugin  == NULL || FWplugin == NULL) return;

	ShutdownPlugins();

	SysCloseLibrary(GSplugin);   GSplugin = NULL;
	SysCloseLibrary(PAD1plugin); PAD1plugin = NULL;
	SysCloseLibrary(PAD2plugin); PAD2plugin = NULL;
	SysCloseLibrary(SPU2plugin); SPU2plugin = NULL;
	SysCloseLibrary(CDVDplugin); CDVDplugin = NULL;
	SysCloseLibrary(DEV9plugin); DEV9plugin = NULL;
	SysCloseLibrary(USBplugin);  USBplugin = NULL;
	SysCloseLibrary(FWplugin);   FWplugin = NULL;
	loadp = false;
}

void PluginsResetGS()
{
	// PADs are tied to the GS window, so shut them down together with the GS.
	
	CLOSE_PLUGIN( PAD1 );
	CLOSE_PLUGIN( PAD2 );

	if( OpenStatus.GS )
	{
		gsClose();
		OpenStatus.GS = false;
	}

	GSshutdown();

	int ret = GSinit();
	if (ret != 0) { Msgbox::Alert("GSinit error: %d", params ret);  }
}
