LosaDev source snapshot 2019

This commit is contained in:
LosaDev committed 2026-11-07 13:30:45 +07:00
commit c198676e97
20588 files changed
+8812878

No files matched your search

@@ -0,0 +1,275 @@
// File.cpp: implementation of the File class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "File.h"
#include <stdio.h>
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
File::File() : m_hFile(INVALID_HANDLE_VALUE), m_dwSizeLow(0), m_dwSizeHigh(0)
{
}
File::File(LPCTSTR fileName, DWORD dwMode, DWORD dwShareMode, DWORD dwCreationDisposition) : m_hFile(INVALID_HANDLE_VALUE), m_dwSizeLow(0), m_dwSizeHigh(0)
{
Open(fileName, dwMode, dwShareMode, dwCreationDisposition);
}
File::~File()
{
Close();
}
//////////////////////////////////////////////////////////////////////
// operation
//////////////////////////////////////////////////////////////////////
BOOL File::Open(const TCHAR* fileName, DWORD dwMode, DWORD dwShareMode, DWORD dwCreationDisposition)
{
Close();
m_hFile = CreateFile(
reinterpret_cast<LPCTSTR>(fileName),
dwMode,
dwShareMode,
NULL,
dwCreationDisposition,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(m_hFile == INVALID_HANDLE_VALUE) return FALSE;
return (m_hFile == INVALID_HANDLE_VALUE) ? FALSE : TRUE;
}
void File::Close()
{
if(m_hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hFile);
m_hFile = NULL;
}
m_dwSizeLow = 0;
m_dwSizeHigh = 0;
}
BOOL File::Move(DWORD dwOrigin, LONG lOffset)
{
if(m_hFile != INVALID_HANDLE_VALUE)
{
DWORD dwResult = SetFilePointer( m_hFile,
lOffset,
NULL,
dwOrigin);
return (dwResult == 0xFFFFFFFF) ? FALSE : TRUE;
}
return FALSE;
}
DWORD File::GetFileSize()
{
if(m_dwSizeLow != 0)
return m_dwSizeLow;
DWORD dwError = 0;
if(m_hFile != INVALID_HANDLE_VALUE)
{
m_dwSizeLow = ::GetFileSize(m_hFile, &m_dwSizeHigh);
if((INVALID_FILE_SIZE == m_dwSizeLow) &&
((dwError = GetLastError()) != NO_ERROR))
return 0;
}
return m_dwSizeLow;
}
//////////////////////////////////////////////////////////////////////
// FileReader class
//////////////////////////////////////////////////////////////////////
FileReader::FileReader(const TCHAR* fileName) : m_nReadType(-1)
{
if(fileName != NULL)
{
Open(fileName);
}
}
BOOL FileReader::Open(const TCHAR* fileName)
{
if(File::Open(fileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING))
{
return (GetBOM() > 0) ? TRUE : FALSE;
}
return FALSE;
}
BOOL FileReader::Read(BYTE* buffer, const DWORD dwLength)
{
DWORD dwWritten = 0;
if(m_hFile == INVALID_HANDLE_VALUE) return FALSE;
if(ReadFile( m_hFile,
buffer,
dwLength,
&dwWritten,
NULL))
{
if(dwLength == dwWritten) return TRUE;
}
return FALSE;
}
BOOL FileReader::Convert(const BYTE* bufferA, const int lengthA, BYTE* bufferB, const int lengthB)
{
switch(m_nReadType)
{
case FILEFORMAT_ANSI :
CopyMemory(bufferB, bufferA, (lengthA > lengthB) ? lengthB : lengthA);
return TRUE;
case FILEFORMAT_UNICODE_UTF8 :
CopyMemory(bufferB, bufferA, (lengthA > lengthB) ? lengthB : lengthA);
return TRUE;
case FILEFORMAT_UNICODE_LE :
WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)bufferA, lengthA, (LPSTR)bufferB, lengthB, NULL, NULL);
return TRUE;
case FILEFORMAT_UNICODE_BE :
return TRUE;
}
return FALSE;
}
int FileReader::GetBOM()
{
if((-1) == m_nReadType)
{
Move(FILE_BEGIN, 0);
WORD BOM = 0;
if(Read(reinterpret_cast<BYTE*>(&BOM), sizeof(WORD)))
{
switch(BOM)
{
case 0xFEFF :
m_nReadType = FILEFORMAT_UNICODE_LE;
break;
case 0xFFFE :
m_nReadType = 0; //FILEFORMAT_UNICODE_BE; not supported
break;
case 0xBBEF :
m_nReadType = FILEFORMAT_UNICODE_UTF8;
Move(FILE_BEGIN, 3);
break;
default:
m_nReadType = FILEFORMAT_ANSI;
Move(FILE_BEGIN, 0);
break;
}
return m_nReadType;
}
return 0;
}
return m_nReadType;
}
DWORD FileReader::GetFileSize()
{
if(0 == m_dwSizeLow)
{
File::GetFileSize();
switch(m_nReadType)
{
case FILEFORMAT_UNICODE_LE :
case FILEFORMAT_UNICODE_BE :
m_dwSizeLow -= (2);
break;
case FILEFORMAT_UNICODE_UTF8 :
m_dwSizeLow -= (3);
break;
default:
break;
}
}
return m_dwSizeLow;
}
//////////////////////////////////////////////////////////////////////
// FileWriter class
//////////////////////////////////////////////////////////////////////
FileWriter::FileWriter(const TCHAR* fileName)
{
if(fileName != NULL)
{
Open(fileName);
}
}
BOOL FileWriter::Open(const TCHAR* fileName, DWORD diposition)
{
if(0 == diposition)
{
diposition = CREATE_NEW;
}
if(File::Open(fileName, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, diposition))
{
WriteHeader();
return TRUE;
}
return FALSE;
}
BOOL FileWriter::Write(const BYTE* buffer, const DWORD dwLength)
{
if(dwLength == 0)
return TRUE;
DWORD dwBytes = 0;
if(WriteFile( m_hFile,
buffer,
dwLength,
&dwBytes,
NULL))
{
return (dwBytes == dwLength) ? TRUE : FALSE;
}
return FALSE;
}
BOOL FileWriter::WriteFormat(const TCHAR* format, ...)
{
TCHAR buffer[4096];
va_list marker;
va_start(marker, format);
_vstprintf_s(buffer, _countof(buffer), format, marker);
va_end(marker);
return Write(reinterpret_cast<BYTE*>(buffer), (DWORD)(_tcslen(buffer)*sizeof(TCHAR)));
}
void FileWriter::WriteHeader()
{
#ifdef _UNICODE
Move(FILE_BEGIN, 0);
WORD BOM = 0xFEFF;
Write(reinterpret_cast<BYTE*>(&BOM), sizeof(BOM));
#endif
}
@@ -0,0 +1,66 @@
#pragma once
// File
class File
{
public:
File();
File(LPCTSTR fileName, DWORD dwMode, DWORD dwShareMode, DWORD dwCreationDisposition);
virtual ~File();
public:
BOOL Open(const TCHAR* fileName, DWORD dwMode, DWORD dwShareMode, DWORD dwCreationDisposition);
void Close();
BOOL Move(DWORD dwOrigin, LONG lOffset);
virtual DWORD GetFileSize();
protected:
enum SaveFileFormat
{
FILEFORMAT_ANSI = 1,
FILEFORMAT_UNICODE_UTF8,
FILEFORMAT_UNICODE_LE,
FILEFORMAT_UNICODE_BE,
};
HANDLE m_hFile;
DWORD m_dwSizeLow, m_dwSizeHigh;
};
// FileReader
class FileReader : public File
{
public:
FileReader(const TCHAR* fileName=NULL);
public:
BOOL Open(const TCHAR* fileName);
BOOL Read(BYTE* buffer, const DWORD dwLength);
BOOL Convert(const BYTE* bufferA, const int lengthA, BYTE* bufferB, const int lengthB);
int GetBOM();
virtual DWORD GetFileSize();
private:
int m_nReadType;
};
// FileWriter
class FileWriter : public File
{
public:
FileWriter(const TCHAR* fileName=NULL);
public:
BOOL Open(const TCHAR* fileName, DWORD diposition=0);
BOOL Write(const BYTE* buffer, const DWORD dwLength);
BOOL WriteFormat(const TCHAR* format, ...);
void WriteHeader();
};
@@ -0,0 +1,460 @@
#include "stdafx.h"
#include <malloc.h>
#include "File.h"
#include "../Minidump/MiniDump.h"
TCHAR MiniDump::m_filename[MAX_PATH];
TCHAR MiniDump::m_folername[MAX_PATH];
CONTEXT MiniDump::m_stackContext;
IMAGEHLP_LINE64 MiniDump::m_stackLine;
STACKFRAME64 MiniDump::m_stackFrame;
BYTE MiniDump::m_stackSymbol[SYM_BUFF_SIZE];
TCHAR MiniDump::m_dumpText[BUFF_SIZE];
BOOL MiniDump::m_symEngInit = FALSE;
typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(
HANDLE hProcess,
DWORD dwPid,
HANDLE hFile,
MINIDUMP_TYPE DumpType,
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
LPTOP_LEVEL_EXCEPTION_FILTER PreviousExceptionFilter = NULL;
LONG WINAPI UnHandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionInfo)
{
// 콜스택저장
MiniDump::PrintCallStack(exceptionInfo);
// Windows 2000 이전에는 따로 DBGHELP를 배포해서 설정해 주어야 한다.
HMODULE DllHandle = LoadLibrary(_T("DBGHELP.DLL"));
if (DllHandle)
{
MINIDUMPWRITEDUMP MiniDumpWriteDump = reinterpret_cast<MINIDUMPWRITEDUMP>(GetProcAddress(DllHandle, "MiniDumpWriteDump"));
if (MiniDumpWriteDump)
{
TCHAR dumpPath[MAX_PATH] = {0};
SYSTEMTIME SystemTime;
GetLocalTime(&SystemTime);
_sntprintf_s(
dumpPath,
_countof(dumpPath),
MAX_PATH,
_T("%s\\%s(%04d%02d%02d-%02d%02d%02d).dmp"),
MiniDump::m_folername,
MiniDump::m_filename,
SystemTime.wYear,
SystemTime.wMonth,
SystemTime.wDay,
SystemTime.wHour,
SystemTime.wMinute,
SystemTime.wSecond);
HANDLE hDumpFile = CreateFile(
dumpPath,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDumpFile != INVALID_HANDLE_VALUE)
{
_MINIDUMP_EXCEPTION_INFORMATION ExceptionInfo;
ExceptionInfo.ThreadId = GetCurrentThreadId();
ExceptionInfo.ExceptionPointers = exceptionInfo;
ExceptionInfo.ClientPointers = NULL;
BOOL bSuccess = MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hDumpFile,
MiniDumpNormal,
&ExceptionInfo,
NULL,
NULL);
if (bSuccess)
{
CloseHandle(hDumpFile);
return EXCEPTION_EXECUTE_HANDLER;
}
CloseHandle(hDumpFile);
}
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
BOOL MiniDump::Begin(const TCHAR* filename)
{
SetErrorMode(SEM_FAILCRITICALERRORS);
TCHAR temp[512];
GetCurrentDirectory(_countof(temp), temp);
_sntprintf_s(m_folername, _countof(m_folername),_T("%s\\dump"), temp);
CreateDirectory(m_folername, NULL);
_tcscpy_s(m_filename, _countof(m_filename), filename); // 디버깅 해볼것
PreviousExceptionFilter = SetUnhandledExceptionFilter(UnHandledExceptionFilter);
return TRUE;
}
BOOL MiniDump::End(VOID)
{
SetUnhandledExceptionFilter(PreviousExceptionFilter);
return TRUE;
}
//////////////////////////////////////////////////////////////////////
// Operations
//////////////////////////////////////////////////////////////////////
// Initializes the symbol engine if needed
void MiniDump::InitSymEng(void)
{
if(FALSE == m_symEngInit)
{
// Set up the symbol engine.
DWORD options = SymGetOptions();
// Turn on line loading.
SymSetOptions(options | SYMOPT_LOAD_LINES);
// Force the invade process flag on.
BOOL bRet = SymInitialize(GetCurrentProcess(), NULL , TRUE);
m_symEngInit = bRet;
}
}
// Helper function to isolate filling out the stack frame, which is CPU specific.
void MiniDump::FillInStackFrame(PCONTEXT contextRecord)
{
// Initialize the STACKFRAME structure.
ZeroMemory(&m_stackFrame, sizeof (STACKFRAME64));
m_stackFrame.AddrPC.Offset = contextRecord->Eip;
m_stackFrame.AddrPC.Mode = AddrModeFlat;
m_stackFrame.AddrStack.Offset = contextRecord->Esp;
m_stackFrame.AddrStack.Mode = AddrModeFlat;
m_stackFrame.AddrFrame.Offset = contextRecord->Ebp;
m_stackFrame.AddrFrame.Mode = AddrModeFlat;
}
//////////////////////////////////////////////////////////////////////
// Operations
//////////////////////////////////////////////////////////////////////
BOOL __stdcall CH_ReadProcessMemory( HANDLE hHandle,
DWORD64 qwBaseAddress,
PVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesRead)
{
return (ReadProcessMemory( GetCurrentProcess(),
(LPCVOID)qwBaseAddress,
lpBuffer,
nSize,
lpNumberOfBytesRead));
}
DWORD Ansi2Wide(const char * szANSI, wchar_t* szWide, int iWideLen)
{
int iRet = MultiByteToWideChar( GetACP(),
0,
szANSI,
-1,
szWide,
iWideLen);
return (iRet);
}
DWORD Wide2Ansi(const wchar_t* szWide, char * szANSI, int iAnsiLen)
{
ZeroMemory(szANSI, iAnsiLen);
int iRet = WideCharToMultiByte(CP_ACP, 0, szWide, -1, szANSI, iAnsiLen, NULL, NULL);
return iRet;
}
// The internal function that does all the stack walking
LPCTSTR MiniDump::InternalGetStackTraceString(DWORD options)
{
// The value that is returned
LPCTSTR szRet;
// The module base address. I look this up right after the stack
// walk to ensure that the module is valid.
DWORD64 modBase;
__try
{
// Initialize the symbol engine in case it isn't initialized.
MiniDump::InitSymEng();
// Note: If the source file and line number functions are used,
// StackWalk can cause an access violation.
BOOL ret = StackWalk64 ( IMAGE_FILE_MACHINE_I386,
GetCurrentProcess(),
GetCurrentThread(),
&m_stackFrame,
&m_stackContext,
CH_ReadProcessMemory,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL);
if((FALSE == ret) || (0 == m_stackFrame.AddrFrame.Offset))
{
szRet = NULL;
__leave;
}
// Before I get too carried away and start calculating
// everything, I need to double-check that the address returned
// by StackWalk really exists. I've seen cases in which
// StackWalk returns TRUE but the address doesn't belong to
// a module in the process.
modBase = SymGetModuleBase64(GetCurrentProcess(), m_stackFrame.AddrPC.Offset);
if(0 == modBase)
{
szRet = NULL;
__leave;
}
int iCurr = 0;
// At a minimum, put in the address.
#ifdef _WIN64
iCurr += wsprintf( m_dumpText + iCurr,
_T( "0x%016X" ),
m_stackFrame.AddrPC.Offset);
#else
iCurr += wsprintf ( m_dumpText + iCurr,
_T("%04X:%08X"),
m_stackContext.SegCs,
m_stackFrame.AddrPC.Offset);
#endif
// Output the parameters?
if(DUMP_PARAMS == (options & DUMP_PARAMS))
{
iCurr += wsprintf( m_dumpText + iCurr,
_T("0x%08X 0x%08X 0x%08X 0x%08X"),
m_stackFrame.Params[ 0 ],
m_stackFrame.Params[ 1 ],
m_stackFrame.Params[ 2 ],
m_stackFrame.Params[ 3 ]);
}
// Output the module name.
if(DUMP_MODULE == (options & DUMP_MODULE))
{
iCurr += wsprintf(m_dumpText + iCurr, _T (" "));
iCurr += GetModuleBaseName( GetCurrentProcess(),
(HINSTANCE)modBase,
m_dumpText + iCurr,
BUFF_SIZE - iCurr);
}
// Output the symbol name?
if(DUMP_SYMBOL == (options & DUMP_SYMBOL))
{
DWORD64 displacement;
// Start looking up the exception address.
PIMAGEHLP_SYMBOL64 symbol = (PIMAGEHLP_SYMBOL64)&m_stackSymbol;
ZeroMemory(symbol , SYM_BUFF_SIZE);
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
symbol->MaxNameLength = SYM_BUFF_SIZE - sizeof(IMAGEHLP_SYMBOL64);
symbol->Address = m_stackFrame.AddrPC.Offset;
if(TRUE == SymGetSymFromAddr64( GetCurrentProcess(),
m_stackFrame.AddrPC.Offset,
&displacement,
symbol))
{
if(options & ~DUMP_SYMBOL)
{
iCurr += wsprintf(m_dumpText + iCurr , _T ( "," ));
}
// Copy no more symbol information than there's room
// for. Symbols are ANSI
int iLen = lstrlenA( symbol->Name );
if(iLen > (BUFF_SIZE - iCurr - (MAX_SYM_SIZE + 50)))
{
// Get some room on the stack to convert the string.
lstrcpyn( m_dumpText + iCurr,
reinterpret_cast<LPCWSTR>(symbol->Name),
BUFF_SIZE - iCurr - 1);
// Gotta leave now
szRet = m_dumpText;
__leave;
}
else
{
if(displacement > 0)
{
iCurr += wsprintf ( m_dumpText + iCurr,
//_T("%S()+%04d byte(s)"), // for unicode
_T("%s()+%04d byte(s)"),
symbol->Name,
displacement);
}
else
{
iCurr += wsprintf ( m_dumpText + iCurr,
//_T("%S"), // for unicode
_T("%s"),
symbol->Name);
}
}
}
else
{
// If the symbol wasn't found, the source file and line
// number won't be found either, so leave now.
szRet = m_dumpText;
__leave;
}
}
// Output the source file and line number information?
if(DUMP_SRCLINE == (options & DUMP_SRCLINE))
{
ZeroMemory(&m_stackLine , sizeof(IMAGEHLP_LINE64));
m_stackLine.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
DWORD dwLineDisp;
if(TRUE == SymGetLineFromAddr64(GetCurrentProcess(),
m_stackFrame.AddrPC.Offset,
&dwLineDisp,
&m_stackLine))
{
if(options & ~DUMP_SRCLINE)
{
iCurr += wsprintf(m_dumpText + iCurr , _T ( "," ));
}
// Copy no more of the source file and line number
// information than there's room for.
int iLen = lstrlenA(m_stackLine.FileName);
if(iLen > (BUFF_SIZE - iCurr - (MAX_PATH + 50)))
{
// Get some room on the stack to convert the string.
lstrcpyn(m_dumpText + iCurr,reinterpret_cast<LPCWSTR>(m_stackLine.FileName), BUFF_SIZE - iCurr - 1);
// Gotta leave now
szRet = m_dumpText;
__leave;
}
else
{
if(dwLineDisp > 0)
{
iCurr += wsprintf( m_dumpText + iCurr,
//_T(" %S, line %04d+%04d byte(s)"),
_T(" %s, line %04d+%04d byte(s)"),
m_stackLine.FileName,
m_stackLine.LineNumber,
dwLineDisp);
}
else
{
iCurr += wsprintf( m_dumpText + iCurr,
//_T(" %S, line %04d"),
_T(" %s, line %04d"),
m_stackLine.FileName,
m_stackLine.LineNumber);
}
}
}
}
szRet = m_dumpText;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
szRet = NULL;
}
return (szRet);
}
LPCTSTR MiniDump::GetFirstStackTraceString(DWORD options, EXCEPTION_POINTERS * exceptionInfo)
{
if(TRUE == IsBadReadPtr(exceptionInfo, sizeof(EXCEPTION_POINTERS*)))
{
// Debug(_T("GetFirstStackTraceString - invalid exceptionInfo\r\n"));
return (NULL);
}
// Get the stack frame filled in.
MiniDump:: FillInStackFrame(exceptionInfo->ContextRecord);
// Copy over the exception pointers fields so I don't corrupt the
// real one.
m_stackContext = *(exceptionInfo->ContextRecord);
return (MiniDump::InternalGetStackTraceString(options));
}
LPCTSTR MiniDump::GetNextStackTraceString(DWORD options, EXCEPTION_POINTERS* exceptionInfo)
{
// All error checking is in InternalGetStackTraceString.
// Assume that GetFirstStackTraceString has already initialized the
// stack frame information.
return (InternalGetStackTraceString(options));
}
void MiniDump::PrintCallStack(struct _EXCEPTION_POINTERS *exceptionInfo)
{
TCHAR dumpPath[MAX_PATH] = {0};
SYSTEMTIME SystemTime;
GetLocalTime(&SystemTime);
_sntprintf_s(
dumpPath,
_countof(dumpPath),
MAX_PATH,
_T("%s\\%s(%04d%02d%02d-%02d%02d%02d).txt"),
MiniDump::m_folername,
MiniDump::m_filename,
SystemTime.wYear,
SystemTime.wMonth,
SystemTime.wDay,
SystemTime.wHour,
SystemTime.wMinute,
SystemTime.wSecond);
FileWriter file;
if(file.Open(dumpPath))
{
// 콜스택 정보
DWORD options = DUMP_MODULE | DUMP_SYMBOL | DUMP_SRCLINE; //DUMP_PARAMS
const TCHAR* dumpText = GetFirstStackTraceString(options, exceptionInfo);
do
{
file.WriteFormat(_T("%s\r\n"), dumpText);
dumpText = GetNextStackTraceString(options, exceptionInfo);
}
while(NULL != dumpText);
// 파일저장
file.Close();
}
}
@@ -0,0 +1,43 @@
#pragma once
#include <DBGHELP.h>
#include <PSAPI.h>
#define DUMP_PARAMS 0x01
#define DUMP_MODULE 0x02
#define DUMP_SYMBOL 0x04
#define DUMP_SRCLINE 0x08
#define BUFF_SIZE 2048
#define SYM_BUFF_SIZE 1024
#define MAX_SYM_SIZE 512
// ¹Ì´Ï´ýÇÁ
class MiniDump
{
public:
static BOOL Begin(const TCHAR* filename);
static BOOL End(VOID);
public:
static void InitSymEng(void);
static void FillInStackFrame(PCONTEXT contextRecord);
static LPCTSTR InternalGetStackTraceString(DWORD options);
static LPCTSTR GetFirstStackTraceString(DWORD options, EXCEPTION_POINTERS * exceptionInfo);
static LPCTSTR GetNextStackTraceString(DWORD options, EXCEPTION_POINTERS* exceptionInfo);
static void PrintCallStack(struct _EXCEPTION_POINTERS *exceptionInfo);
public:
static TCHAR m_filename[MAX_PATH];
static TCHAR m_folername[MAX_PATH];
static TCHAR m_dumpText[BUFF_SIZE];
static BYTE m_stackSymbol[SYM_BUFF_SIZE];
static IMAGEHLP_LINE64 m_stackLine;
static CONTEXT m_stackContext;
static STACKFRAME64 m_stackFrame;
static BOOL m_symEngInit;
};