• You've discovered RedGuides, an EverQuest multi-boxing and scripting community 🧙‍♀️⚙️. We want you to play several EQ characters at once, come join us and say hello! 👋

  • A TLP without truebox has thawed (Very Vanilla ready)
    Frostreaver

MQ2Winamp

grunion

New member
Joined
Apr 7, 2005
RedCents
found this over on the MQ2 boards. Thought it was a neat plugin so I thought I'd share it :)

commands:
Rich (BB code):
Syntax: 

/mp3 { play [cd|#|url http://|file *] | pause | stop | next | prev | shuffle | repeat | vol # | volup | voldown | info | playlist | browser } 

Commands: 

/mp3                    - Display help and display MP3 window 
/mp3 play               - Play current selection 
/mp3 play #             - Play specified track 
/mp3 play cd            - Play currently inserted CD 
/mp3 play url http://   - Play specified url 
/mp3 play file *        - Play specified file 
/mp3 pause              - Pause current playback 
/mp3 stop               - Stop playback 
/mp3 next               - Next song in playlist 
/mp3 prev               - Prev song in playlist 
/mp3 shuffle            - Toggle random play of playlist 
/mp3 repeat             - Continuous play of playlist when end reached 
/mp3 vol #              - Direct setting of volume (0-255) 
/mp3 volup              - Nudge volume up (like moving the slider) 
/mp3 voldown            - Nudge volume down (like moving the slider) 
/mp3 info               - Display quick info (Title atm) 
/mp3 playlist           - Toggle display of WinAMP playlist window 
/mp3 browser            - Toggle display of WinAMP minibrowser

Have winamp running to use plugin - OR - To have it automatically load winamp if its closed from withing eq, the appropriate lines must be in the macroquest.ini as follows (change path as needed):

Code:
[Application Paths] 
winamp="c:\\program files\\winamp\\winamp.exe"

Source:
Rich (BB code):
/*****************************************************************************
mq2winamp.dll: MacroQuest2's extension DLL for EverQuest
Copyright (C) 2004 Lasher

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License, version 2, as
  published by the Free Software Foundation.
  
    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.
******************************************************************************/

//
// MQ2Winamp.cpp : Winamp plugin by Lasher
//
// v0.1		02/08/04 - Inital code
// v0.2		02/08/04 - Now with SuperPiMP(tm) technology (so advanced, so amazing, we won't even tell you what it is)
// v0.3		02/08/04 - Added in MF's windowing code.
// v0.4		02/18/04 - New variables and CComboWnd updates (title updates!)
// v0.5		02/18/04 - Volume fixes, window optimizing and more variable goodness.
// v0.6		02/20/04 - Fancy detours and hooks for UI updates.  (don't unload if you use MQ2Labels - ctd)
// v0.61	02/20/04 - More variables for your enjoyment.
// v0.62	02/22/04 - Major UI updates and optimizations
// v0.63	02/22/04 - Volume slider improvements (thanks MF)
// v0.70	05/24/04 - Inital (read: sloppy) MQ2Data translations
// v0.71	05/25/04 - MQ2Data cleanup. Variables back!  (thanks DKAA)
// v0.72	06/11/04 - Title cleanups. More variable goodness.
//

#include "../MQ2Plugin.h"
#include "frontend.h"
#include "MQ2Winamp.h"

PreSetup("MQ2Winamp");

// Window Declarations
class CMP3Wnd : public CCustomWnd
{
public:
	CMP3Wnd():CCustomWnd("MusicPlayerWnd")
	{
		CloseButton=(CButtonWnd*)GetChildItem("CloseButton");
		
		BackButton=(CButtonWnd*)GetChildItem("BackButton");
		PlayButton=(CButtonWnd*)GetChildItem("PlayButton");
		PauseButton=(CButtonWnd*)GetChildItem("PauseButton");
		StopButton=(CButtonWnd*)GetChildItem("StopButton");
		NextButton=(CButtonWnd*)GetChildItem("NextButton");
		RandomButton=(CButtonWnd*)GetChildItem("RandomButton");
		
		AddButton=(CButtonWnd*)GetChildItem("AddButton");
		DeleteButton=(CButtonWnd*)GetChildItem("DeleteButton");
		ClearButton=(CButtonWnd*)GetChildItem("ClearButton");
		SaveButton=(CButtonWnd*)GetChildItem("SaveButton");
		
		Volume=(CSliderWnd*)GetChildItem("Volume");
		
		SongSelector=(CComboWnd*)GetChildItem("SongSelector");
		DurationMin=(CSidlScreenWnd*)GetChildItem("DurationMin");
		SongTimer=(CGaugeWnd*)GetChildItem("SongTimer");
		
		SetWndNotification(CMP3Wnd);
	}
	
	~CMP3Wnd()
	{
	}
	
	int WndNotification(CXWnd *pWnd, unsigned int Message, void *unknown)
	{
		if (pWnd==(CXWnd*)CloseButton)
		{
			if (Message==XWM_LCLICK)
				pXWnd()->Show(0,0);
			else
				DebugSpew("CloseButton message %Xh / %d",Message,Message);
			
		} else if (pWnd==(CXWnd*)BackButton) {
			if (Message==XWM_LCLICK)
				mp3prev(NULL,NULL);
			else
				DebugSpew("BackButton message %Xh / %d",Message,Message);
			
		} else if (pWnd==(CXWnd*)PlayButton) {
			if (Message==XWM_LCLICK)
				mp3play(NULL,NULL);
			else
				DebugSpew("PlayButton message %Xh / %d",Message,Message);
			
		} else if (pWnd==(CXWnd*)PauseButton) {
			if (Message==XWM_LCLICK)
				mp3pause(NULL,NULL);
			else
				DebugSpew("PauseButton message %Xh / %d",Message,Message);
			
		} else if (pWnd==(CXWnd*)StopButton) {
			if (Message==XWM_LCLICK)
				mp3stop(NULL,NULL);
			else
				DebugSpew("StopButton message %Xh / %d",Message,Message);
			
		} else if (pWnd==(CXWnd*)NextButton) {
			if (Message==XWM_LCLICK)
				mp3next(NULL,NULL);
			else
				DebugSpew("NextButton message %Xh / %d",Message,Message);
			
		} else if (pWnd==(CXWnd*)RandomButton) {
			if (Message==XWM_LCLICK)
				mp3shuffle(NULL,NULL);
			else
				DebugSpew("RandomButton message %Xh / %d",Message,Message);
			
			// Add to the playlist
		} else if (pWnd==(CXWnd*)AddButton) {
			if (Message==XWM_LCLICK)
				pXWnd()->Show(0,0);
			
			// Delete entry from playlist
		} else if (pWnd==(CXWnd*)DeleteButton) {
			if (Message==XWM_LCLICK)
				pXWnd()->Show(0,0);
			
			// Clear the playlist
		} else if (pWnd==(CXWnd*)ClearButton) {
			if (Message==XWM_LCLICK)
				pXWnd()->Show(0,0);
			
			// Save the playlist
		} else if (pWnd==(CXWnd*)SaveButton) {
			if (Message==XWM_LCLICK)
				pXWnd()->Show(0,0);
			
		} else if (pWnd==(CXWnd*)SongSelector) {
			if (Message==XWM_LCLICK)
				DebugSpew("SongSelector message %Xh / %d",Message,Message);
			
		} else if (pWnd==(CXWnd*)Volume) {
			if (Message==14) {
				CHAR szTemp[MAX_STRING] = {0};
				int vol = (int)unknown * 12;
				itoa(vol,szTemp,10);
				//sprintf(szTemp,"%i",unknown);
				mp3volume(NULL,szTemp);
			}
		}
		mp3windowupdate(NULL,NULL);
		return CSidlScreenWnd::WndNotification(pWnd,Message,unknown);
	};

	CButtonWnd *CloseButton;
	
	CButtonWnd *BackButton;
	CButtonWnd *PlayButton;
	CButtonWnd *PauseButton;
	CButtonWnd *StopButton;
	CButtonWnd *NextButton;
	CButtonWnd *RandomButton;
	
	CButtonWnd *AddButton;
	CButtonWnd *DeleteButton;
	CButtonWnd *ClearButton;
	CButtonWnd *SaveButton;
	
	CComboWnd *SongSelector;
	CSliderWnd *Volume;
	CSidlScreenWnd *DurationMin;
	CGaugeWnd *SongTimer;
};

CMP3Wnd *MP3Wnd=0;



VOID mp3pause(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,WINAMP_BUTTON3,0);
	/**
	int res = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISPLAYING);
	if (res==3) {
	WriteChatColor("Paused",USERCOLOR_DEFAULT );
	
	  } else if (res==1) {
	  WriteChatColor("Unpaused",USERCOLOR_DEFAULT );
	  }
	**/
}


VOID mp3volumeup(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,WINAMP_VOLUMEUP,0);
}

VOID mp3volumedown(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,WINAMP_VOLUMEDOWN,0);
}

VOID mp3playlist(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,WINAMP_OPTIONS_PLEDIT,0);
}

VOID mp3play(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,WINAMP_BUTTON2,0);
}

VOID mp3stop(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,WINAMP_BUTTON4,0);
}

VOID mp3next(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,WINAMP_BUTTON5,0);
}

VOID mp3prev(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,WINAMP_BUTTON1,0);
}

VOID mp3playcd(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,40323,0);
}

VOID mp3browser(PSPAWNINFO pChar,PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	SendMessage(hwnd_winamp,WM_COMMAND,40298,0);
}

VOID mp3info(PSPAWNINFO pChar,PCHAR szLine) {
	CHAR szTemp1[MAX_STRING] = {0};
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	CHAR szTitle[MAX_STRING] = {0};
	GetWindowText(hwnd_winamp,szTitle,sizeof(szTitle));
	char *p;
	p = szTitle+strlen(szTitle)-8;
	while (p >= szTitle)
	{
		if (!strnicmp(p,"- Winamp",8)) break;
		p--;
	}
	if (p >= szTitle) p--;
	while (p >= szTitle && *p == ' ') p--;
	*++p=0;
	sprintf(szTemp1,"Title: %s",szTitle);
	WriteChatColor(szTemp1, USERCOLOR_DEFAULT );
}


VOID mp3shuffle(PSPAWNINFO pChar,PCHAR szLine) {
    HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	int rep;
	int pos=SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GET_SHUFFLE);
	if (pos==1)
	{
		rep=0;
		//WriteChatColor("Turning shuffle off",USERCOLOR_DEFAULT );
	}
	else if (pos==0)
	{
		rep=1;
		//WriteChatColor("Turning shuffle on",USERCOLOR_DEFAULT );
	}
	SendMessage(hwnd_winamp,WM_WA_IPC,rep,IPC_SET_SHUFFLE);
	mp3windowupdate(pChar,hwnd_winamp);
}

VOID mp3repeat(PSPAWNINFO pChar,PCHAR szLine) {
    HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	int rep;
	int pos=SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GET_REPEAT);
	if (pos==1)
	{
		rep=0;
		//WriteChatColor("Turning repeat off",USERCOLOR_DEFAULT );
	}
	else if (pos==0)
	{
		rep=1;
		//WriteChatColor("Turning repeat on",USERCOLOR_DEFAULT );
	}
	SendMessage(hwnd_winamp,WM_WA_IPC,rep,IPC_SET_REPEAT);
}

VOID mp3volume(PSPAWNINFO pChar, PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	CHAR szArg1[MAX_STRING] = {0};
	int newvol;
	GetArg(szArg1,szLine,1);
	newvol = atoi(szArg1);
	newvol = (int)(newvol*2.55);
	if (newvol>255) newvol = 255;
	if (szArg1) {
		SendMessage(hwnd_winamp,WM_USER, newvol, IPC_SETVOLUME);
	}
}

VOID mp3playtrack(PSPAWNINFO pChar, PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	CHAR szArg1[MAX_STRING] = {0};
	GetArg(szArg1,szLine,1);
	if (szArg1) {
		SendMessage(hwnd_winamp,WM_USER, atoi(szArg1)-1, IPC_SETPLAYLISTPOS);
		mp3play(pChar,szLine);
	}
}

VOID mp3playurl(PSPAWNINFO pChar, PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	CHAR szArg1[MAX_STRING] = {0};
	GetArg(szArg1,szLine,1);
	if (strstr(szArg1,"http://")) {
		COPYDATASTRUCT cds;
		cds.dwData = IPC_PLAYFILE;
		cds.lpData = szArg1;
		cds.cbData = sizeof(szArg1)+1;
		int iTotTracks = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTLENGTH);
		SendMessage(hwnd_winamp,WM_COPYDATA,(WPARAM)NULL,(LPARAM)&cds);
		SendMessage(hwnd_winamp,WM_USER, iTotTracks, IPC_SETPLAYLISTPOS);
		mp3play(pChar,szLine);
	}
}

VOID mp3playfile(PSPAWNINFO pChar, PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	CHAR szArg1[MAX_STRING] = {0};
	GetArg(szArg1,szLine,1);
	COPYDATASTRUCT cds;
	cds.dwData = IPC_PLAYFILE;
	cds.lpData = szArg1;
	cds.cbData = sizeof(szArg1)+1;
	int iTotTracks = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTLENGTH);
	SendMessage(hwnd_winamp,WM_COPYDATA,(WPARAM)NULL,(LPARAM)&cds);
	SendMessage(hwnd_winamp,WM_USER, iTotTracks, IPC_SETPLAYLISTPOS);
	mp3play(pChar,szLine);
}

DWORD parmMP3(PCHAR szVar, PCHAR szOutput, PSPAWNINFO pChar) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	DWORD i;
	CHAR szTemp[MAX_STRING] = {0};
	
	if (!strstr(szVar,")")) {
		DebugSpewNoFile("PMP - Bad $count() '%s'",szVar);
		return PMP_ERROR_BADPARM;
		
		// mp3(artist)
	} else if (strstr(szVar,"mp3(artist)")) {
		i = 10;
		//LPDWORD TempHandle, temp;
		//TempHandle = new(DWORD);
		//temp = new(DWORD);
		// int index = SendMessage(hwnd_winamp, WM_USER, 0, 125);
		/** CHAR szArg1[40] = {0};
		M3UINFO m3u;
		m3u.FileName = "potime.mp3";
		m3u.Metadata = "artist";
		m3u.ret = szArg1;
		m3u.retlen = sizeof(szArg1)+1;
		**/
		
		//LPCVOID lpFileName=(LPCVOID)SendMessage(hwnd_winamp,WM_WA_IPC,&m3u,IPC_GET_EXTENDED_FILE_INFO);
		//GetWindowThreadProcessId(hwnd_winamp, TempHandle);
		//HANDLE hWinamp = OpenProcess(PROCESS_ALL_ACCESS,false,*TempHandle);
		//char *dat = (char*) malloc(256);
		//ReadProcessMemory(hWinamp, lpFileName, dat,256,temp);
		//CloseHandle(hWinamp);
		//free(TempHandle);
		//free(temp);
		//		strcat(szOutput,dat);
		DebugSpewAlways("Attempting currentsong");
		char *currentSong = (char*)SendMessage(hwnd_winamp,WM_WA_IPC, SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS) ,IPC_GETPLAYLISTFILE);
		DebugSpewAlways("Initializing playedSong");
		itemRecord playedSong={currentSong,};
		DebugSpewAlways("attempting ipc_getinfo");
		SendMessage(hwnd_winamp,WM_ML_IPC,(WPARAM)&playedSong,ML_IPC_GETFILEINFO);
		//SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&playedSong,ML_IPC_GETFILEINFO);
		DebugSpewAlways("Initializing plartist");
		char *plrating = getRecordExtendedItem(&playedSong, "RATING");
		DebugSpewAlways("formatting output");
		sprintf(szTemp,"rating: %s",plrating);
		DebugSpewAlways("Sending output");
		strcat(szOutput,szTemp);
		
		// mp3(bitrate)
	} else if (strstr(szVar,"mp3(bitrate)")) {
		i = 11;
		int iBitRate = SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETINFO);
		itoa(iBitRate,szTemp,10);
		strcat(szOutput,szTemp);
		
		// mp3(channels)
	} else if (strstr(szVar,"mp3(channels)")) {
		i = 14;
		int iChannels = SendMessage(hwnd_winamp,WM_WA_IPC,2,IPC_GETINFO);
		itoa(iChannels,szTemp,10);
		strcat(szOutput,szTemp);
		
		// $mp3(length)
	} else if (strstr(szVar,"mp3(length)")) {
		i = 10;
		int iSongLength = SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETOUTPUTTIME);
		int iMin = iSongLength/60;
		int iSec = iSongLength%60;
		sprintf(szTemp,"%d:%d",iMin,iSec);
		strcat(szOutput,szTemp);
		
	} else if (strstr(szVar,"mp3(length,min)")) {
		i = 14;
		int iSongLength = SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETOUTPUTTIME);
		iSongLength /= 60;
		itoa(iSongLength,szTemp,10);
		strcat(szOutput,szTemp);
		
	} else if (strstr(szVar,"mp3(length,sec)")) {
		i = 14;
		int iSongLength = SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETOUTPUTTIME);
		iSongLength %= 60;
		itoa(iSongLength,szTemp,10);
		strcat(szOutput,szTemp);
		
		// $mp3(position)
	} else if (strstr(szVar,"mp3(position)")) {
		i = 12;
		int iSongPosition = (SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)/1000);
		itoa(iSongPosition,szTemp,10);
		strcat(szOutput,szTemp);
		
		// $mp3(position,min)
	} else if (strstr(szVar,"mp3(position,min)")) {
		i = 16;
		int iSongPosition = (SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)/1000);
		iSongPosition /= 60;
		itoa(iSongPosition,szTemp,10);
		strcat(szOutput,szTemp);
		
		// $mp3(position,sec)
	} else if (strstr(szVar,"mp3(position,sec)")) {
		i = 16;
		int iSongPosition = (SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)/1000);
		iSongPosition %= 60;
		if (iSongPosition<10) strcat(szOutput,"0");
		sprintf(szTemp,"%d",iSongPosition);
		strcat(szOutput,szTemp);
		
		// mp3(samplerate)
	} else if (strstr(szVar,"mp3(samplerate)")) {
		i = 14;
		int iSampleRate = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETINFO);
		itoa(iSampleRate,szTemp,10);
		strcat(szOutput,szTemp);
		
		// mp3(track,curr) - Current track in the playlist
	} else if (strstr(szVar,"mp3(track,curr)")) {
		i = 14;
		int iCurrTrack = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS);
		itoa(iCurrTrack,szTemp,10);
		strcat(szOutput,szTemp);
		
		// mp3(track,total) - Total number of tracks in the playlist
	} else if (strstr(szVar,"mp3(track,total)")) {
		i = 15;
		int iTotTracks = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTLENGTH);
		itoa(iTotTracks,szTemp,10);
		strcat(szOutput,szTemp);
		
		// mp3(url)
	} else if (strstr(szVar,"mp3(url)")) {
		i = 7;
		LPDWORD TempHandle, temp;
		TempHandle = new(DWORD);
		temp = new(DWORD);
		int index = SendMessage(hwnd_winamp, WM_USER, 0, 125); 
		LPCVOID lpFileName=(LPCVOID)SendMessage(hwnd_winamp, WM_USER, index, 211);
		GetWindowThreadProcessId(hwnd_winamp, TempHandle);
		HANDLE hWinamp = OpenProcess(PROCESS_ALL_ACCESS,false,*TempHandle);
		char *dat = (char*) malloc(256);
		ReadProcessMemory(hWinamp, lpFileName, dat,256,temp);
		CloseHandle(hWinamp);
		free(TempHandle);
		free(temp);
		strcat(szOutput,dat);
		
		// mp3(filename)
	} else if (strstr(szVar,"mp3(filename)")) {
		i = 12;
		LPDWORD TempHandle, temp;
		TempHandle = new(DWORD);
		temp = new(DWORD);
		int index = SendMessage(hwnd_winamp, WM_USER, 0, 125); 
		LPCVOID lpFileName=(LPCVOID)SendMessage(hwnd_winamp, WM_USER, index, 211);
		GetWindowThreadProcessId(hwnd_winamp, TempHandle);
		HANDLE hWinamp = OpenProcess(PROCESS_ALL_ACCESS,false,*TempHandle);
		char *dat = (char*) malloc(256);
		ReadProcessMemory(hWinamp, lpFileName, dat,256,temp);
		CloseHandle(hWinamp);
		free(TempHandle);
		free(temp);
		strcat(szOutput,dat);
		
		// mp3(title)
	} else if (strstr(szVar,"mp3(title)")) {
		i = 9;
		CHAR szTitle[MAX_STRING] = {0};
		GetWindowText(hwnd_winamp,szTitle,sizeof(szTitle));
		char *p;
		p = szTitle+strlen(szTitle)-8;
		while (p >= szTitle)
		{
			if (!strnicmp(p,"- Winamp",8)) break;
			p--;
		}
		if (p >= szTitle) p--;
		while (p >= szTitle && *p == ' ') p--;
		*++p=0;
		strcat(szOutput,szTitle);
	}
	return i;
}


VOID mp3windowupdate(PSPAWNINFO pChar,HWND hwnd_winamp) {
	if (!hwnd_winamp) {
		hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	}
	if (MP3Wnd) {
		int res = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISPLAYING);
		if (res==3) {
			((CSidlScreenWnd*)MP3Wnd->PauseButton)->Checked=true;
		} else if (res==1) {
			((CSidlScreenWnd*)MP3Wnd->PauseButton)->Checked=false;
			((CSidlScreenWnd*)MP3Wnd->PauseButton)->Enabled=true;
			((CSidlScreenWnd*)MP3Wnd->StopButton)->Enabled=true;
		} else {
			((CSidlScreenWnd*)MP3Wnd->PauseButton)->Checked=false;
			((CSidlScreenWnd*)MP3Wnd->PauseButton)->Enabled=false;
			((CSidlScreenWnd*)MP3Wnd->StopButton)->Enabled=false;
		}
		if (SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_SHUFFLE)) {
			((CSidlScreenWnd*)MP3Wnd->RandomButton)->Checked=true;
		} else {
			((CSidlScreenWnd*)MP3Wnd->RandomButton)->Checked=false;
		}
		((CSidlScreenWnd*)MP3Wnd->DeleteButton)->Enabled=false;
		((CSidlScreenWnd*)MP3Wnd->AddButton)->Enabled=false;
		((CSidlScreenWnd*)MP3Wnd->ClearButton)->Enabled=false;
		((CSidlScreenWnd*)MP3Wnd->SaveButton)->Enabled=false;
		int iVolume = SendMessage(hwnd_winamp,WM_USER, -666, IPC_SETVOLUME);
		((CSliderWnd*)MP3Wnd->Volume)->SetValue((int)(ceil((iVolume/2.55)/12)));
		((CSliderWnd*)MP3Wnd->Volume)->UpdateThumb(); 
		((CSliderWnd*)MP3Wnd->Volume)->ZLayer=-1;
		((CXWnd*)MP3Wnd)->Show(1,1);
	}
}



VOID mp3(PSPAWNINFO pChar, PCHAR szLine) {
	HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
	if (!hwnd_winamp) Exec(pChar,"winamp bg");
	
	CHAR szArg1[MAX_STRING] = {0};
	CHAR szArg2[MAX_STRING] = {0};
	CHAR szArg3[MAX_STRING] = {0};
	GetArg(szArg1,szLine,1);
	GetArg(szArg2,szLine,2);
	GetArg(szArg3,szLine,3);
	
	if(!strcmp(szArg1,"play")) {
		if (szArg2[0]==0)            mp3play(pChar,szLine);
		else {
			if (!strcmp(szArg2,"cd"))   mp3playcd(pChar,szLine);
			else if (!strcmp(szArg2,"url"))   mp3playurl(pChar,szArg3);
			else if (!strcmp(szArg2,"file"))   mp3playfile(pChar,szArg3);
			else                  mp3playtrack(pChar,szArg2);
		}
	}
	else if(!strcmp(szArg1,"pause"))   mp3pause(pChar,szLine);
	else if(!strcmp(szArg1,"stop"))      mp3stop(pChar,szLine);
	else if(!strcmp(szArg1,"next"))      mp3next(pChar,szLine);
	else if(!strcmp(szArg1,"prev"))      mp3prev(pChar,szLine);
	else if(!strcmp(szArg1,"shuffle"))   mp3shuffle(pChar,szLine);
	else if(!strcmp(szArg1,"repeat"))   mp3repeat(pChar,szLine);
	else if(!strcmp(szArg1,"vol"))      mp3volume(pChar,szArg2);
	else if(!strcmp(szArg1,"volup"))   mp3volumeup(pChar,szLine);
	else if(!strcmp(szArg1,"voldown"))   mp3volumedown(pChar,szLine);
	else if(!strcmp(szArg1,"info"))      mp3info(pChar,szLine);
	else if(!strcmp(szArg1,"playlist"))   mp3playlist(pChar,szLine);
	else if(!strcmp(szArg1,"browser"))   mp3browser(pChar,szLine);
	else {
		WriteChatColor("FORMAT: /mp3 { play [cd|#|url http://|file *] | pause | stop | next | prev | shuffle | repeat | vol # | volup | voldown | info | playlist | browser }",USERCOLOR_DEFAULT);
	}
	mp3windowupdate(pChar,hwnd_winamp);
}

char *getRecordExtendedItem(itemRecord *item, char *name)
{
	int x=0;
	if (item->extended_info) for (x = 0; item->extended_info[x]; x ++)
	{
		if (!stricmp(item->extended_info[x],name))
			return item->extended_info[x]+strlen(name)+1;
	}
	return NULL;
}

struct _ID_PMP
{ DWORD ID;PCHAR PMP; }
Id_PMP[] = {
	{76,	"${MP3.length_min}"},
	{77,	"${MP3.length_sec}"},
	{78,	"${MP3.position_min}"},
	{79,	"${MP3.position_sec}"},
	{-1,NULL}
}; 


class CLabelHook {
public:
	VOID Draw_Trampoline(VOID);
	VOID Draw_Detour(VOID)
	{
		PCSIDLWND pThisLabel;
		__asm {mov [pThisLabel], ecx};
		Draw_Trampoline();
		CHAR Buffer[MAX_STRING] = {0};
		BOOL Found=FALSE;
		DWORD index;
		
		if ((DWORD)pThisLabel->SidlPiece<100) {
			for (index=0;Id_PMP[index].ID>0 && !Found;index++) {
				if (Id_PMP[index].ID==(DWORD)pThisLabel->SidlPiece) {
					strcpy(Buffer,Id_PMP[index].PMP);
					ParseMacroParameter(((PCHARINFO)pCharData)->pSpawn,Buffer);
					if (!strcmp(Buffer,"NULL"))
						Buffer[0]=0;
					Found=TRUE;
				}
			}
		}
		if (Found) SetCXStr(&(pThisLabel->WindowText),Buffer);
	}
}; 

DETOUR_TRAMPOLINE_EMPTY(VOID CLabelHook::Draw_Trampoline(VOID));

class MQ2MP3Type : public MQ2Type
{
public:

	enum MP3Members
	{
		bitrate=1,
		channels,
		filename,
		length,
		length_min,
		length_sec,
		position,
		position_min,
		position_sec,
		samplerate,
		title,
		title_raw,
		track_curr,
		track_total,
		url
	};
	
	MQ2MP3Type():MQ2Type("mp3")
	{
		TypeMember(bitrate);
		TypeMember(channels);
		TypeMember(filename);
		TypeMember(length);
		TypeMember(length_min);
		TypeMember(length_sec);
		TypeMember(position);
		TypeMember(position_min);
		TypeMember(position_sec);
		TypeMember(samplerate);
		TypeMember(title);
		TypeMember(title_raw);
		TypeMember(track_curr);
		TypeMember(track_total);
		TypeMember(url);
	}
	
	~MQ2MP3Type()
	{
	}
	
	bool GetMember(MQ2VARPTR VarPtr, PCHAR Member, PCHAR Index, MQ2TYPEVAR &Dest) 
	{ 
		PMQ2TYPEMEMBER pMember=MQ2MP3Type::FindMember(Member); 
		HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
		//DWORD i;
		LPDWORD TempHandle, temp;
		int index;
		HANDLE hWinamp;
		LPCVOID lpFileName;
		char *dat = (char*) malloc(256);
		CHAR szTemp[MAX_STRING] = {0};
		CHAR szTitle[MAX_STRING] = {0};
		char *p;
		char *x;
		
		if (!pMember) 
			return false; 
		
		switch ((MP3Members)pMember->ID) 
		{ 
		case bitrate: 
			Dest.Int= 
				SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETINFO); 
			Dest.Type=pIntType; 
			return true; 
		case channels: 
			Dest.Int= 
				SendMessage(hwnd_winamp,WM_WA_IPC,2,IPC_GETINFO); 
			Dest.Type=pIntType; 
			return true; 
		case filename:
			TempHandle = new(DWORD);
			temp = new(DWORD);
			index = SendMessage(hwnd_winamp, WM_USER, 0, 125); 
			lpFileName=(LPCVOID)SendMessage(hwnd_winamp, WM_USER, index, 211);
			GetWindowThreadProcessId(hwnd_winamp, TempHandle);
			hWinamp = OpenProcess(PROCESS_ALL_ACCESS,false,*TempHandle);
			ReadProcessMemory(hWinamp, lpFileName, dat,256,temp);
			CloseHandle(hWinamp);
			free(TempHandle);
			free(temp);
			Dest.Ptr= &dat[0];
			Dest.Type=pStringType;
			return true; 
		case length: 
			int iSec,iMin,iSongLength;
			iSongLength = SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETOUTPUTTIME);
			iMin = iSongLength/60;
			iSec = iSongLength%60;
			sprintf(szTemp,"%d:%d",iMin,iSec);
			Dest.Ptr= &szTemp[0];
			Dest.Type=pStringType;
			return true; 
		case length_min: 
			Dest.Int= 
				SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETOUTPUTTIME);
            Dest.Int /= 60;
			Dest.Type=pIntType; 
			return true; 
		case length_sec: 
			Dest.Int= 
				SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETOUTPUTTIME);
            Dest.Int %= 60;
			if (Dest.Int<0) {
				Dest.Int=00;
			}
			Dest.Type=pIntType; 
			return true; 
		case position: 
			Dest.Int= 
				(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)/1000); 
			Dest.Type=pIntType; 
			return true; 
		case position_min: 
			Dest.Int= 
				(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)/1000);
            Dest.Int /= 60;
			Dest.Type=pIntType; 
			return true; 
		case position_sec: 
			Dest.Int= 
				(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)/1000);
            Dest.Int %= 60;
			Dest.Type=pIntType; 
			return true; 
		case samplerate: 
			Dest.Int= 
				SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETINFO);
			Dest.Type=pIntType; 
			return true; 
		case title:
			GetWindowText(hwnd_winamp,szTitle,sizeof(szTitle));
			p = szTitle+strlen(szTitle)-8;
			while (p >= szTitle)
			{
				if (!strnicmp(p,"- Winamp",8)) break;
				p--;
			}
			if (p >= szTitle) p--;
			while (p >= szTitle && *p == ' ') p--;
			*++p=0;
			x = strtok (szTitle,".");
			if (x != NULL ) { x = strtok (NULL,"."); }
			x = strchr(x, ' ');
			++x;
			while (x != NULL ) {
				strcat(szTemp,x);
				x = strtok (NULL,".");
				if (x == NULL) break;
				strcat(szTemp,".");
			}
			strcpy(szTitle,szTemp);
			Dest.Ptr= &szTitle;
			Dest.Type=pStringType;
			return true;
		case title_raw:
			GetWindowText(hwnd_winamp,szTitle,sizeof(szTitle));
			p = szTitle+strlen(szTitle)-8;
			while (p >= szTitle)
			{
				if (!strnicmp(p,"- Winamp",8)) break;
				p--;
			}
			if (p >= szTitle) p--;
			while (p >= szTitle && *p == ' ') p--;
			*++p=0;
			Dest.Ptr= &szTitle;
			Dest.Type=pStringType;
			return true;
		case track_curr: 
			Dest.Int= 
				SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS);
			Dest.Type=pIntType; 
			return true; 
		case track_total: 
			Dest.Int= 
				SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTLENGTH);
			Dest.Type=pIntType; 
			return true;
		case url:
			TempHandle = new(DWORD);
			temp = new(DWORD);
			index = SendMessage(hwnd_winamp, WM_USER, 0, 125); 
			lpFileName=(LPCVOID)SendMessage(hwnd_winamp, WM_USER, index, 211);
			GetWindowThreadProcessId(hwnd_winamp, TempHandle);
			hWinamp = OpenProcess(PROCESS_ALL_ACCESS,false,*TempHandle);
			char *dat = (char*) malloc(256);
			ReadProcessMemory(hWinamp, lpFileName, dat,256,temp);
			CloseHandle(hWinamp);
			free(TempHandle);
			free(temp);
			Dest.Ptr= &dat[0];
			Dest.Type=pStringType;
			return true;
    } 
	//CloseHandle(hWinamp);
	//free(TempHandle);
	//free(temp);
    return false; 
} 

bool ToString(MQ2VARPTR VarPtr, PCHAR Destination) 
{ 
    return false; 
} 
bool FromData(MQ2VARPTR &VarPtr, MQ2TYPEVAR &Source) 
{ 
    return false; 
} 
bool FromString(MQ2VARPTR &VarPtr, PCHAR Source) 
{ 
    return false; 
} 
}; 

BOOL dataMP3(PCHAR szName, MQ2TYPEVAR &Ret) 
{ 
    Ret.DWord=1; 
    Ret.Type=pMP3Type; 
    return true; 
} 


PLUGIN_API VOID InitializePlugin(VOID)
{
	DebugSpewAlways("Initializing MQ2Winamp");
	AddCommand("/mp3",mp3);
	//AddParm("mp3(",parmMP3);
	AddMQ2Data("MP3",dataMP3);
	pMP3Type = new MQ2MP3Type;
	   //void (CLabelHook::*pfDetour)(VOID) = CLabelHook::Draw_Detour;
	   //void (CLabelHook::*pfTrampoline)(VOID) = CLabelHook::Draw_Trampoline;
	   //AddDetour(CLabel__Draw,*(PBYTE*)&pfDetour,*(PBYTE*)&pfTrampoline);
	//EasyClassDetour(CLabel__Draw,CLabelHook,Draw_Detour,VOID,(VOID),Draw_Trampoline);
	EzDetour(CLabel__Draw,CLabelHook::Draw_Detour,CLabelHook::Draw_Trampoline);
}

PLUGIN_API VOID ShutdownPlugin(VOID)
{
	DebugSpewAlways("Shutting down MQ2Winamp");
	RemoveDetour(CLabel__Draw);
	RemoveCommand("/mp3");
	//RemoveParm("mp3(");
	RemoveMQ2Data("MP3");
	delete pMP3Type;
}

PLUGIN_API VOID OnCleanUI(VOID)
{
	DebugSpewAlways("MQ2WndTest::OnCleanUI()");
	if (MP3Wnd)
	{
		delete MP3Wnd;
		MP3Wnd=0;
	}
}

PLUGIN_API VOID SetGameState(DWORD GameState)
{
	if (GameState==GAMESTATE_INGAME && !MP3Wnd)
	{
		if (pSidlMgr->FindScreenPieceTemplate("MusicPlayerWnd")) {
			MP3Wnd=new CMP3Wnd;
			((CXWnd*)MP3Wnd)->Show(0,0);
		}
	}
}

PLUGIN_API VOID OnReloadUI()
{
	if (!MP3Wnd)
	{
		if (pSidlMgr->FindScreenPieceTemplate("MusicPlayerWnd")) {
			MP3Wnd=new CMP3Wnd;
			((CXWnd*)MP3Wnd)->Show(0,0);
		}
	}
}

PLUGIN_API VOID OnPulse(VOID)
{
	
	static DWORD MP3UIDelay=0;
	
	if (++MP3UIDelay>10) MP3UIDelay=0;
	
    if (MP3Wnd && ((PCSIDLWND)MP3Wnd)->Show && !MP3UIDelay) {
		HWND hwnd_winamp = FindWindow("Winamp v1.x",NULL);
		CHAR szTitle[MAX_STRING] = {0};
		CHAR szTemp[MAX_STRING] = {0};
		char *x;
		GetWindowText(hwnd_winamp,szTitle,sizeof(szTitle));
		char *p;
		p = szTitle+strlen(szTitle)-8;
		while (p >= szTitle)
		{
			if (!strnicmp(p,"- Winamp",8)) break;
			p--;
		}
		if (p >= szTitle) p--;
		while (p >= szTitle && *p == ' ') p--;
		*++p=0;
		x = strtok (szTitle,".");
		if (x != NULL ) { x = strtok (NULL,"."); }
		x = strchr(x, ' ');
		++x;
		while (x != NULL ) {
			strcat(szTemp,x);
			x = strtok (NULL,".");
			if (x == NULL) break;
			strcat(szTemp,".");
		}
		strcpy(szTitle,szTemp);
		if (strcmp(gszCurrentTitle, szTitle)) {
			strcpy(gszCurrentTitle, szTitle);
			((CComboWnd*)MP3Wnd->SongSelector)->DeleteAll();
			((CComboWnd*)MP3Wnd->SongSelector)->InsertChoice(szTitle);
			((CComboWnd*)MP3Wnd->SongSelector)->SetChoice(0);
		}
	}
}

frontend.h
Rich (BB code):
#ifndef _WAFE_H_
#define _WAFE_H_

#define WM_WA_IPC WM_USER
#define IPC_GETVERSION 0
#define IPC_DELETE 101
#define IPC_STARTPLAY 102
#define IPC_ISPLAYING 104
#define IPC_GETOUTPUTTIME 105
#define IPC_JUMPTOTIME 106
#define IPC_WRITEPLAYLIST 120
#define IPC_SETPLAYLISTPOS 121
#define IPC_SETVOLUME 122
#define IPC_SETPANNING 123
#define IPC_GETLISTLENGTH 124
#define IPC_SETSKIN 200
#define IPC_GETSKIN 201
#define IPC_EXECPLUG 202
#define IPC_GETPLAYLISTFILE 211
#define IPC_GETPLAYLISTTITLE 212
#define IPC_GETLISTPOS 125
#define IPC_GETINFO 126 
#define IPC_GETEQDATA 127 
#define IPC_SETEQDATA 128
#define IPC_ADDBOOKMARK 129
#define IPC_RESTARTWINAMP 135
#define IPC_MBOPEN 241
#define IPC_INETAVAILABLE 242
#define IPC_UPDTITLE 243
#define IPC_CHANGECURRENTFILE 245
#define IPC_GETMBURL 246
#define IPC_REFRESHPLCACHE 247
#define IPC_MBBLOCK 248
#define IPC_MBOPENREAL 249
#define IPC_GET_SHUFFLE 250
#define IPC_GET_REPEAT 251
#define IPC_SET_SHUFFLE 252
#define IPC_SET_REPEAT 253
#define IPC_PLAYFILE 100
#define IPC_CHDIR 103
#define WINAMP_OPTIONS_EQ               40036
#define WINAMP_OPTIONS_PLEDIT           40040
#define WINAMP_VOLUMEUP                 40058
#define WINAMP_VOLUMEDOWN               40059
#define WINAMP_FFWD5S                   40060
#define WINAMP_REW5S                    40061
#define WINAMP_BUTTON1                  40044
#define WINAMP_BUTTON2                  40045
#define WINAMP_BUTTON3                  40046
#define WINAMP_BUTTON4                  40047
#define WINAMP_BUTTON5                  40048
#define WINAMP_BUTTON1_SHIFT            40144
#define WINAMP_BUTTON2_SHIFT            40145
#define WINAMP_BUTTON3_SHIFT            40146
#define WINAMP_BUTTON4_SHIFT            40147
#define WINAMP_BUTTON5_SHIFT            40148
#define WINAMP_BUTTON1_CTRL             40154
#define WINAMP_BUTTON2_CTRL             40155
#define WINAMP_BUTTON3_CTRL             40156
#define WINAMP_BUTTON4_CTRL             40157
#define WINAMP_BUTTON5_CTRL             40158
#define WINAMP_FILE_PLAY                40029
#define WINAMP_OPTIONS_PREFS            40012
#define WINAMP_OPTIONS_AOT              40019
#define WINAMP_HELP_ABOUT               40041
#endif

mq2winamp.h
Rich (BB code):
#define WM_ML_IPC WM_USER+0x1000
#define ML_IPC_GETFILEINFO 0x0200

CHAR gszCurrentTitle[MAX_STRING] = {0};

//stuffs
VOID mp3pause(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3volumeup(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3volumedown(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3playlist(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3play(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3stop(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3next(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3prev(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3playcd(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3browser(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3info(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3shuffle(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3repeat(PSPAWNINFO pChar,PCHAR szLine);
VOID mp3volume(PSPAWNINFO pChar, PCHAR szLine);
VOID mp3playtrack(PSPAWNINFO pChar, PCHAR szLine);
VOID mp3playurl(PSPAWNINFO pChar, PCHAR szLine);
VOID mp3playfile(PSPAWNINFO pChar, PCHAR szLine);

DWORD parmMP3(PCHAR szVar, PCHAR szOutput, PSPAWNINFO pChar);
VOID mp3windowupdate(PSPAWNINFO pChar,HWND hwnd_winamp);
VOID mp3(PSPAWNINFO pChar, PCHAR szLine);

BOOL dataMP3(PCHAR szName, MQ2TYPEVAR &Ret);

class CMP3Wnd;

class MQ2MP3Type *pMP3Type=0; 

typedef struct _M3UINFO {
	CHAR		FileName[11];
	CHAR		Metadata[7];
	CHAR		ret[40];
	DWORD		retlen;
} M3UINFO, *PM3UINFO;

/*typedef struct
{
	int		bitrate;
	int		channels;
	char	*filename;
	char	*length;
	int		length_min;
	int		length_sec;
	int		position;
	int		position_min;
	int		position_sec;
	int		samplerate;
	char	*title;
	int		track_curr;
	int		track_total;
	char	*url;
} MP3struct; */


typedef struct
{
  char *filename;
  char *title;
  char *album;
  char *artist;
  char *comment;
  char *genre;
  int year;
  int track;
  int length;
  char **extended_info;
  // currently defined extended columns (while they are stored internally as integers
  // they are passed using extended_info as strings):
  // use getRecordExtendedItem and setRecordExtendedItem to get/set.
  // for your own internal use, you can set other things, but the following values
  // are what we use at the moment. Note that setting other things will be ignored
  // by ML_IPC_DB*.
  // 
  //"RATING" file rating. can be 1-5, or 0 or empty for undefined
  //"PLAYCOUNT" number of file plays.
  //"LASTPLAY" last time played, in standard time_t format
  //"LASTUPD" last time updated in library, in standard time_t format
  //"FILETIME" last known file time of file, in standard time_t format
  //"FILESIZE" last known file size, in kilobytes.
  //"BITRATE" file bitrate, in kbps

} itemRecord;

typedef struct 
{
  itemRecord *Items;
  int Size;
  int Alloc;
} itemRecordList;

char *getRecordExtendedItem(itemRecord *item, char *name);


//MP3struct mymp3data = {0};
I'm not taking any kind of credit for this, but just thought it was neat and wanted to pass it along :) I tried to attach a compiled dll, but for whatever reason the upload page is timing out on me.
 
Last edited:
You forgot to post frontend.h :eek:

...and MQ2Winamp.h...
 
MQ2Winamp

Users who are viewing this thread

Back
Top
Cart