|
|
/****************************************************************************
* * wdmaud.c * * WDM Audio mapper * * Copyright (C) Microsoft Corporation, 1997 - 1999 All Rights Reserved. * * History * 5-12-97 - Noel Cross (NoelC) * ***************************************************************************/
#include <stdarg.h>
#include "wdmdrv.h"
#include "mixer.h"
#ifdef DEBUG
typedef struct tag_MSGS { ULONG ulMsg; char * pString; } ERROR_MSGS, *PERROR_MSGS;
#define MAPERR(_x_) { _x_, #_x_ }
ERROR_MSGS MsgTable[] = {
//
// Standard error messages
//
MAPERR(MMSYSERR_ERROR), MAPERR(MMSYSERR_BADDEVICEID), MAPERR(MMSYSERR_NOTENABLED), MAPERR(MMSYSERR_ALLOCATED), MAPERR(MMSYSERR_INVALHANDLE), MAPERR(MMSYSERR_NODRIVER), MAPERR(MMSYSERR_NOMEM), MAPERR(MMSYSERR_NOTSUPPORTED), MAPERR(MMSYSERR_BADERRNUM), MAPERR(MMSYSERR_INVALFLAG), MAPERR(MMSYSERR_INVALPARAM), MAPERR(MMSYSERR_HANDLEBUSY),
MAPERR(MMSYSERR_INVALIDALIAS), MAPERR(MMSYSERR_BADDB), MAPERR(MMSYSERR_KEYNOTFOUND), MAPERR(MMSYSERR_READERROR), MAPERR(MMSYSERR_WRITEERROR), MAPERR(MMSYSERR_DELETEERROR), MAPERR(MMSYSERR_VALNOTFOUND), MAPERR(MMSYSERR_NODRIVERCB), MAPERR(MMSYSERR_MOREDATA), MAPERR(MMSYSERR_LASTERROR),
//
// Wave error messages
//
MAPERR(WAVERR_BADFORMAT), MAPERR(WAVERR_STILLPLAYING), MAPERR(WAVERR_UNPREPARED), MAPERR(WAVERR_SYNC), MAPERR(WAVERR_LASTERROR),
//
// Midi Error messages
//
MAPERR(MIDIERR_UNPREPARED), MAPERR(MIDIERR_STILLPLAYING), MAPERR(MIDIERR_NOMAP), MAPERR(MIDIERR_NOTREADY), MAPERR(MIDIERR_NODEVICE), MAPERR(MIDIERR_INVALIDSETUP), MAPERR(MIDIERR_BADOPENMODE), MAPERR(MIDIERR_DONT_CONTINUE), MAPERR(MIDIERR_LASTERROR),
//
// Timer errors
//
MAPERR(TIMERR_NOCANDO), MAPERR(TIMERR_STRUCT),
//
// Joystick error return values
//
MAPERR(JOYERR_PARMS), MAPERR(JOYERR_NOCANDO), MAPERR(JOYERR_UNPLUGGED),
//
// MCI Error return codes.
//
MAPERR(MCIERR_INVALID_DEVICE_ID), MAPERR(MCIERR_UNRECOGNIZED_KEYWORD), MAPERR(MCIERR_UNRECOGNIZED_COMMAND), MAPERR(MCIERR_HARDWARE), MAPERR(MCIERR_INVALID_DEVICE_NAME), MAPERR(MCIERR_OUT_OF_MEMORY), MAPERR(MCIERR_DEVICE_OPEN), MAPERR(MCIERR_CANNOT_LOAD_DRIVER), MAPERR(MCIERR_MISSING_COMMAND_STRING), MAPERR(MCIERR_BAD_INTEGER), MAPERR(MCIERR_PARSER_INTERNAL), MAPERR(MCIERR_DRIVER_INTERNAL), MAPERR(MCIERR_MISSING_PARAMETER), MAPERR(MCIERR_UNSUPPORTED_FUNCTION), MAPERR(MCIERR_FILE_NOT_FOUND), MAPERR(MCIERR_DEVICE_NOT_READY), MAPERR(MCIERR_INTERNAL), MAPERR(MCIERR_DRIVER), MAPERR(MCIERR_CANNOT_USE_ALL), MAPERR(MCIERR_MULTIPLE), MAPERR(MCIERR_EXTENSION_NOT_FOUND), MAPERR(MCIERR_OUTOFRANGE), MAPERR(MCIERR_FLAGS_NOT_COMPATIBLE), //sphelling?
MAPERR(MCIERR_FILE_NOT_SAVED), MAPERR(MCIERR_DEVICE_TYPE_REQUIRED), MAPERR(MCIERR_DEVICE_LOCKED), MAPERR(MCIERR_DUPLICATE_ALIAS), MAPERR(MCIERR_BAD_CONSTANT), MAPERR(MCIERR_MUST_USE_SHAREABLE), MAPERR(MCIERR_MISSING_DEVICE_NAME), MAPERR(MCIERR_BAD_TIME_FORMAT), MAPERR(MCIERR_NO_CLOSING_QUOTE), MAPERR(MCIERR_DUPLICATE_FLAGS), MAPERR(MCIERR_INVALID_FILE), MAPERR(MCIERR_NULL_PARAMETER_BLOCK), MAPERR(MCIERR_UNNAMED_RESOURCE), MAPERR(MCIERR_NEW_REQUIRES_ALIAS), MAPERR(MCIERR_NOTIFY_ON_AUTO_OPEN), MAPERR(MCIERR_NO_ELEMENT_ALLOWED), MAPERR(MCIERR_NONAPPLICABLE_FUNCTION), MAPERR(MCIERR_ILLEGAL_FOR_AUTO_OPEN), MAPERR(MCIERR_FILENAME_REQUIRED), MAPERR(MCIERR_EXTRA_CHARACTERS), MAPERR(MCIERR_DEVICE_NOT_INSTALLED), MAPERR(MCIERR_GET_CD), MAPERR(MCIERR_SET_CD), MAPERR(MCIERR_SET_DRIVE), MAPERR(MCIERR_DEVICE_LENGTH), MAPERR(MCIERR_DEVICE_ORD_LENGTH), MAPERR(MCIERR_NO_INTEGER),
MAPERR(MCIERR_WAVE_OUTPUTSINUSE), MAPERR(MCIERR_WAVE_SETOUTPUTINUSE), MAPERR(MCIERR_WAVE_INPUTSINUSE), MAPERR(MCIERR_WAVE_SETINPUTINUSE), MAPERR(MCIERR_WAVE_OUTPUTUNSPECIFIED), MAPERR(MCIERR_WAVE_INPUTUNSPECIFIED), MAPERR(MCIERR_WAVE_OUTPUTSUNSUITABLE), MAPERR(MCIERR_WAVE_SETOUTPUTUNSUITABLE), MAPERR(MCIERR_WAVE_INPUTSUNSUITABLE), MAPERR(MCIERR_WAVE_SETINPUTUNSUITABLE),
MAPERR(MCIERR_SEQ_DIV_INCOMPATIBLE), MAPERR(MCIERR_SEQ_PORT_INUSE), MAPERR(MCIERR_SEQ_PORT_NONEXISTENT), MAPERR(MCIERR_SEQ_PORT_MAPNODEVICE), MAPERR(MCIERR_SEQ_PORT_MISCERROR), MAPERR(MCIERR_SEQ_TIMER), MAPERR(MCIERR_SEQ_PORTUNSPECIFIED), MAPERR(MCIERR_SEQ_NOMIDIPRESENT),
MAPERR(MCIERR_NO_WINDOW), MAPERR(MCIERR_CREATEWINDOW), MAPERR(MCIERR_FILE_READ), MAPERR(MCIERR_FILE_WRITE), MAPERR(MCIERR_NO_IDENTITY), //
// Mixer return values
//
MAPERR(MIXERR_INVALLINE), MAPERR(MIXERR_INVALCONTROL), MAPERR(MIXERR_INVALVALUE), MAPERR(MIXERR_LASTERROR),
{0xDEADBEEF,"DEADBEEF"}, //
// Don't walk off the end of the list
//
{0,NULL}, {0,"Unknown"} };
//-----------------------------------------------------------------------------
// Globals that affect debug output:
//-----------------------------------------------------------------------------
//
// The documentation relies on these two variables being next to each other
// with uiDebugLevel first. Do not separate them.
//
// Default to displaying all "Warning" messages
UINT uiDebugLevel = DL_WARNING ;
// Default to breaking on all "Error" messages
UINT uiDebugBreakLevel = DL_ERROR ;
char szReturningErrorStr[]="Ret Err %X:%s"; // for storing the deviceinfo's in debug.
//PDINODE gpdiActive=NULL;
//PDINODE gpdiFreeHead=NULL;
//PDINODE gpdiFreeTail=NULL;
//INT giFree=0;
//INT giAlloc=0;
//INT giFreed=0;
//
// Make header for these functions....
//
char *MsgToAscii(ULONG ulMsg) { PERROR_MSGS pTable=MsgTable;
while(pTable->pString != NULL) { if (pTable->ulMsg==ulMsg) return pTable->pString; pTable++; } pTable++; //
// If we get to the end of the list advance the pointer and return
// "Unknown"
//
return pTable->pString; }
VOID wdmaudDbgBreakPoint() { DbgBreak(); }
//
// This routine will format the start of the string. But, before it does that
// it will check to see if the user should even be seeing this message.
//
// uiMsgLevel is the flags in the code that classify the message. This value
// is used if and only if the user is filtering on that class of messages.
//
// If the message is to be displayed, the return value will be non-zero so that
// the message in the code will be displayed. See the macro DPF.
//
UINT wdmaudDbgPreCheckLevel(UINT uiMsgLevel,char *pFunction,int iLine) { char szBuf[24]; UINT uiRet=0;
//
// Read this like: if there is a bit set in the upper 3 bytes of the uiDebugLevel
// variable, then the user is viewing messages of a specific type. We only
// want to show those messages.
//
if( (uiDebugLevel&FA_MASK) ) { //
// Yes, the user filtering on a particular class of messages. Did
// we find one to display? We look at the message flags to determine this.
//
if( (uiMsgLevel&FA_MASK) & (uiDebugLevel&FA_MASK) ) { //
// Yes, we found a message of the right class. Is it at the right
// level for the user to see?
//
if( (uiMsgLevel&DL_MASK) <= (uiDebugLevel&DL_MASK) ) { // Yes.
uiRet=1; } } } else {
// The user is not viewing a specific type of message "class". Do we have
// a message level worth displaying?
if( (uiMsgLevel&DL_MASK) <= (uiDebugLevel&DL_MASK) ) { // Yes.
uiRet=1; } }
// Now just check to see if we need to display on this call.
if( uiRet ) { // Yes. Every message needs to start where it's from!
OutputDebugStringA("WDMAUD.DRV "); OutputDebugStringA(pFunction); wsprintfA(szBuf,"(%d)",iLine); OutputDebugStringA(szBuf);
// Now lable it's type.
switch(uiMsgLevel&DL_MASK) { case DL_ERROR: OutputDebugStringA(" Error "); break; case DL_WARNING: OutputDebugStringA(" Warning "); break; case DL_TRACE: OutputDebugStringA(" Trace "); break; case DL_MAX: OutputDebugStringA(" Max "); break; default: break; } // when uiRet is positive, we've displayed the header info. Tell the
// macro that we're in display mode.
}
return uiRet; }
UINT wdmaudDbgPostCheckLevel(UINT uiMsgLevel) { UINT uiRet=0; // char szBuf[32];
// Always finish the line.
// wsprintfA(szBuf," &DL=%08X",&uiDebugLevel);
// OutputDebugStringA(szBuf);
#ifdef HTTP
OutputDebugStringA(", see \\\\debugtips\\msgs\\wdmauds.htm\n"); #else
OutputDebugStringA("\n"); #endif
//
// Ok, here is the scoop. uiDebugBreakLevel is set to DL_ERROR (0) by default
// thus, any time we get an error message of DL_ERROR level we will break.
//
// Also, uiDebugBreakLevel can be set by the user to DL_WARNING or DL_TRACE
// or DL_MAX. If so, any time we encounter a message with this debug level
// we will break in the debugger.
//
//
if( (uiMsgLevel&DL_MASK) <= uiDebugBreakLevel ) { // The user wants to break on these messages.
DbgBreak(); uiRet = 1; }
return uiRet; }
VOID FAR __cdecl wdmaudDbgOut ( LPSTR lpszFormat, ... ) { char buf[256]; va_list va;
va_start(va, lpszFormat); wvsprintfA(buf, lpszFormat, va); va_end(va); OutputDebugStringA(buf); }
#endif // DEBUG
MMRESULT IsValidMidiDataListEntry( LPMIDIDATALISTENTRY pMidiDataListEntry ) { MMRESULT mmr;
if ( IsBadWritePtr( (LPVOID)(pMidiDataListEntry),sizeof(MIDIDATALISTENTRY) ) ) { DPF(DL_ERROR|FA_ALL, ("Corrupted MidiDataListEntry %X",pMidiDataListEntry) ); return MMSYSERR_INVALPARAM; } #ifdef DEBUG
if( pMidiDataListEntry->dwSig != MIDIDATALISTENTRY_SIGNATURE ) { DPF(DL_ERROR|FA_ALL, ("Invalid MidiDataListEntry Signature %X",pMidiDataListEntry) ); return MMSYSERR_INVALPARAM; } #endif
if( (mmr=IsValidOverLapped(pMidiDataListEntry->pOverlapped)) != MMSYSERR_NOERROR ) { return mmr; } return MMSYSERR_NOERROR; }
////////////////////////////////////////////////////////////////////////////////
//
// IsValidPrepareWaveHeader
//
// This routine is available in debug or retail to validate that we've got a valid
// structure. In retail, we ask the OS if we've got a valid memory pointer. In
// debug we also check other fields
//
// See WAVEPREPAREDATA structure
//
// returns MMSYSERR_NOERROR on success, error code otherwise.
//
MMRESULT IsValidPrepareWaveHeader( PWAVEPREPAREDATA pPrepare ) { MMRESULT mmr;
if ( IsBadWritePtr( (LPVOID)(pPrepare),sizeof(PWAVEPREPAREDATA) ) ) { DPF(DL_ERROR|FA_ALL, ("Corrupted PrepareData %X",pPrepare) ); return MMSYSERR_INVALPARAM; } #ifdef DEBUG
if ( pPrepare->dwSig != WAVEPREPAREDATA_SIGNATURE ) { DPF(DL_ERROR|FA_ALL, ("Invalid PrepareData signature!") ); return MMSYSERR_INVALPARAM; } #endif
if( (mmr=IsValidOverLapped(pPrepare->pOverlapped)) != MMSYSERR_NOERROR ) { return mmr; } return MMSYSERR_NOERROR; }
////////////////////////////////////////////////////////////////////////////////
//
// IsValidOverlapped
//
// Validates the overlapped structure.
//
// returns MMSYSERR_NOERROR on success, error code on failure.
//
MMRESULT IsValidOverLapped( LPOVERLAPPED lpol ) { if ( IsBadWritePtr( (LPVOID)(lpol),sizeof(OVERLAPPED) ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid Overlapped structure %X",lpol) ); return MMSYSERR_INVALPARAM; } if( lpol->hEvent == NULL ) { DPF(DL_ERROR|FA_ASSERT,("Invalid hEvent Overlapped=%08X",lpol) ); return MMSYSERR_INVALPARAM; }
return MMSYSERR_NOERROR; }
////////////////////////////////////////////////////////////////////////////////
//
// IsValidDeviceState
//
// This routine is used in both debug and retail. In retail it validates that
// the pointer that is passed in is the correct size and type. Under debug
// it checks other fields.
//
// See DEVICESTATE structure
//
// returns MMSYSERR_NOERROR on success, error code otherwise.
//
MMRESULT IsValidDeviceState( LPDEVICESTATE lpDeviceState, BOOL bFullyConfigured ) { if ( IsBadWritePtr( (LPVOID)(lpDeviceState),sizeof(DEVICESTATE) ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid DeviceState %X",lpDeviceState) ); return MMSYSERR_INVALPARAM; } if( lpDeviceState->csQueue == NULL ) { DPF(DL_ERROR|FA_ASSERT,("Invalid csQueue in DeviceState %08X",lpDeviceState) ); return MMSYSERR_INVALPARAM; }
#ifdef DEBUG
if( lpDeviceState->dwSig != DEVICESTATE_SIGNATURE ) { DPF(DL_ERROR|FA_ALL, ("Invalid DeviceState dwSig %08X",lpDeviceState) ); return MMSYSERR_INVALPARAM; }
if( bFullyConfigured ) { //
// Now, check to see that the items in the structure looks good.
//
if( ( lpDeviceState->hevtExitThread == NULL ) || ( lpDeviceState->hevtExitThread == (HANDLE)FOURTYEIGHT ) ) { DPF(DL_ERROR|FA_ASSERT,("Invalid hevtExitThread in DeviceState %08X",lpDeviceState) ); return MMSYSERR_INVALPARAM; } if( (lpDeviceState->hevtQueue == NULL) || (lpDeviceState->hevtQueue == (HANDLE)FOURTYTWO) || (lpDeviceState->hevtQueue == (HANDLE)FOURTYTHREE) ) { DPF(DL_ERROR|FA_ASSERT,("Invalid hevtQueue in DeviceState %08X",lpDeviceState) ); return MMSYSERR_INVALPARAM; } } #endif
return MMSYSERR_NOERROR; }
////////////////////////////////////////////////////////////////////////////////
//
// IsValidDeviceInfo
//
// In retail, we validate that we have a pointer of the correct size and type.
// In debug we walk our active deviceinfo list and see if we can find it. If
// we can't, we'll look in our freed list to see if it's there. Basically, when
// someone frees a deviceinfo structure, we add it to the freed list. After the
// freed list grows to 100 in length, we start rolling them off the list and freeing
// them. On shutdown, we clean them all up.
//
// See DEVICEINFO Structure
//
// returns MMSYSERR_NOERROR on success, error code otherwise.
//
MMRESULT IsValidDeviceInfo( LPDEVICEINFO lpDeviceInfo ) { LPDEVICEINFO lpdi; if ( IsBadWritePtr( (LPVOID)(lpDeviceInfo),sizeof(DEVICEINFO) ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid DeviceInfo %X",lpDeviceInfo) ); return MMSYSERR_INVALPARAM; } #ifdef DEBUG
if( lpDeviceInfo->dwSig != DEVICEINFO_SIGNATURE ) { DPF(DL_ERROR|FA_ALL,("Invalid DeviceInfo %08x Signature!",lpDeviceInfo) ); return MMSYSERR_INVALPARAM; } #endif
return MMSYSERR_NOERROR; }
////////////////////////////////////////////////////////////////////////////////
//
// IsValidWaveHeader
//
// In retail we validate that this pointer is of the correct size and type. In
// debug we validate some flags and look for signatures.
//
// See WAVEHDR structure
//
// returns MMRERR_NOERROR on success, Error code otherwise.
//
// Note:
//
// To do this, the LPWAVEHDR structure is defined in mmsystem.h, thus we can not
// add a signiture to that structure. But, we do add info to the reserved field
// that is a WAVEPREPAREDATA structure. That structure has a signiture. Thus,
// we'll add that check to this routine.
//
// To pass, the wave header must be a write pointer of the correct size. The dwFLags
// field must not have any extra flags in it and the reserved field must be a
// WAVEPREPAREDATA pointer.
//
MMRESULT IsValidWaveHeader( LPWAVEHDR pWaveHdr ) { PWAVEPREPAREDATA pwavePrepareData;
if ( IsBadWritePtr( (LPVOID)(pWaveHdr),sizeof(WAVEHDR) ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid pWaveHdr pointer %X",pWaveHdr) ); return MMSYSERR_INVALPARAM; } #ifdef DEBUG
if( pWaveHdr->dwFlags & ~(WHDR_DONE|WHDR_PREPARED|WHDR_BEGINLOOP|WHDR_ENDLOOP|WHDR_INQUEUE) ) { DPF(DL_ERROR|FA_ALL,("Ivalid dwFlags %08x in pWaveHdr %08X", pWaveHdr->dwFlags,pWaveHdr) ); return MMSYSERR_INVALPARAM; } #endif
/*
if (!(pWaveHdr->dwFlags & WHDR_PREPARED)) { DPF(DL_ERROR|FA_ASSERT,("Unprepared header %08X",pWaveHdr) ); return( WAVERR_UNPREPARED ); } */ if ((DWORD_PTR)pWaveHdr->reserved == (DWORD_PTR)NULL) { return( WAVERR_UNPREPARED ); } else { if ( IsBadWritePtr( (LPVOID)(pWaveHdr->reserved),sizeof(WAVEPREPAREDATA) ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid pWaveHdr->reserved %X",pWaveHdr->reserved) ); return MMSYSERR_INVALPARAM; } } #ifdef DEBUG
pwavePrepareData = (PWAVEPREPAREDATA)pWaveHdr->reserved; if( pwavePrepareData->dwSig != WAVEPREPAREDATA_SIGNATURE ) { DPF(DL_ERROR|FA_ALL,("Invalid Signature in WAVEPREPAREDATA structure!") ); return MMSYSERR_INVALPARAM; } #endif
return MMSYSERR_NOERROR; }
MMRESULT IsValidMidiHeader( LPMIDIHDR pMidiHdr ) { if ( IsBadWritePtr( (LPVOID)(pMidiHdr),sizeof(MIDIHDR) ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid pMidiHdr %X",pMidiHdr) ); return MMSYSERR_INVALPARAM; } return MMSYSERR_NOERROR; }
MMRESULT IsValidWaveOpenDesc( LPWAVEOPENDESC pwod ) { if ( IsBadWritePtr( (LPVOID)(pwod),sizeof(WAVEOPENDESC) ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid pwod %X",pwod) ); return MMSYSERR_INVALPARAM; } return MMSYSERR_NOERROR; }
////////////////////////////////////////////////////////////////////////////////
//
// IsValidMixerInstance
//
// Validates that the pointer is the correct type and size. Debug checks for
// the correct signature.
//
// returns TRUE on success FALSE otherwise.
//
MMRESULT IsValidMixerInstance( LPMIXERINSTANCE lpmi ) { LPMIXERINSTANCE currentinstance;
if ( IsBadWritePtr( (LPVOID)(lpmi),sizeof(MIXERINSTANCE) ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid MixerInstance structure %X",lpmi) ); return MMSYSERR_INVALPARAM; } #ifdef DEBUG
if( lpmi->dwSig != MIXERINSTANCE_SIGNATURE ) { DPF(DL_ERROR|FA_ALL,("Invalid Signature in MixerInstance %08X",lpmi) ); return MMSYSERR_INVALPARAM; } #endif
for (currentinstance=pMixerDeviceList;currentinstance!=NULL;currentinstance=currentinstance->Next) if (currentinstance==lpmi) return MMSYSERR_NOERROR;
DPF(DL_ERROR|FA_ALL, ("Invalid Instance passed to mxdMessage in dwUser!") );
// Since tracking down the WINMM bug that causes this assert to fire is
// proving very difficult and fwong (current winmm owner) is blowing it off
// and I (joeball) have more important things to do, I am turning off this assert.
// We still spew, but simply fail the api calls that don't have what we think
// is valid instance data.
// I currently think it is one of 2 things: either the winmm usage count is wrapping -
// higher than 255 - a possibility because of the PNP recursive calls
// in winmm, OR, the CleanUpDesertedHandles in the UpdateClientPnpInfo is
// getting stuff closed just before it is used. Either is a very likely
// possibility.
//DPFASSERT( 0 );
return MMSYSERR_INVALPARAM; }
////////////////////////////////////////////////////////////////////////////////
//
// IsValidDeviceInterface
//
// This routine simply validates that we have a pointer to at least 1 byte that
// is readable. In debug we check to make sure that the string is not absurd.
//
// returns TRUE on success FALSE otherwise.
//
BOOL IsValidDeviceInterface( LPCWSTR DeviceInterface ) { if ( IsBadReadPtr( (LPVOID)(DeviceInterface),1 ) ) { DPF(DL_ERROR|FA_ALL, ("Invalid DeviceInterface string %08X",DeviceInterface) ); return FALSE; } #ifdef DEBUG
if( (sizeof(WCHAR)*lstrlenW(DeviceInterface)) > 4096 ) { DPF(DL_ERROR|FA_ALL, ("Invalid DeviceInterface string %08X",DeviceInterface) ); return FALSE; } #endif
return TRUE; }
|