windows-nt/Source/XPSP1/NT/sdktools/restools/rlt32/rw/mac/mac.cpp
2020-09-26 16:20:57 +08:00

750 lines
23 KiB
C++

//+---------------------------------------------------------------------------
//
// File: mac.cpp
//
// Contents: Implementation for the Macintosh Read/Write module
//
// History: 23-Aug-94 alessanm created
//
//----------------------------------------------------------------------------
#include <afxwin.h>
#include <limits.h>
#include <malloc.h>
#include "..\common\rwdll.h"
#include "..\common\m68k.h"
#include "..\common\helper.h"
#include "mac.h"
/////////////////////////////////////////////////////////////////////////////
// Initialization of MFC Extension DLL
#include "afxdllx.h" // standard MFC Extension DLL routines
static AFX_EXTENSION_MODULE NEAR extensionDLL = { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
// General Declarations
#define RWTAG "MAC"
static ULONG gType;
static ULONG gLng;
static ULONG gResId;
static WCHAR gwszResId[256];
HINSTANCE g_IODLLInst = 0;
DWORD (PASCAL * g_lpfnGetImage)(HANDLE, LPCSTR, LPCSTR, DWORD, LPVOID, DWORD);
DWORD (PASCAL * g_lpfnUpdateResImage)(HANDLE, LPSTR, LPSTR, DWORD, DWORD, LPVOID, DWORD);
HANDLE (PASCAL * g_lpfnHandleFromName)(LPCSTR);
/////////////////////////////////////////////////////////////////////////////
// Public C interface implementation
//[registration]
extern "C"
BOOL FAR PASCAL RWGetTypeString(LPSTR lpszTypeName)
{
strcpy( lpszTypeName, RWTAG );
return FALSE;
}
//=============================================================================
//
// To validate a mac res binary file we will walk the resource header and see
// if it matches with what we have.
//
//=============================================================================
extern "C"
BOOL FAR PASCAL RWValidateFileType (LPCSTR lpszFilename)
{
BOOL bRet = FALSE;
TRACE("MAC.DLL: RWValidateFileType()\n");
CFile file;
// we Open the file to see if it is a file we can handle
if (!file.Open( lpszFilename, CFile::typeBinary | CFile::modeRead | CFile::shareDenyNone ))
return bRet;
// Check if this is a MAC Resource file ...
if(IsMacResFile( &file ))
bRet = TRUE;
file.Close();
return bRet;
}
//=============================================================================
//
// We will walk the resource header, walk the resource map and then normalize
// the Mac it to Windows id and pass this info to the RW.
//
//=============================================================================
extern "C"
DllExport
UINT
APIENTRY
RWReadTypeInfo(
LPCSTR lpszFilename,
LPVOID lpBuffer,
UINT* puiSize
)
{
TRACE("MAC.DLL: RWReadTypeInfo()\n");
UINT uiError = ERROR_NO_ERROR;
BYTE far * lpBuf = (BYTE far *)lpBuffer;
UINT uiBufSize = *puiSize;
CFile file;
int iFileNameLen = strlen(lpszFilename)+1;
if (!file.Open(lpszFilename, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone))
return ERROR_FILE_OPEN;
UINT uiBufStartSize = uiBufSize;
////////////////////////////////////
// Check if it is a valid mac file
// Is a Mac Resource file ...
if(IsMacResFile( &file )) {
// load the file in memory
// NOTE: WIN16 This might be to expensive in memory allocation
// on a 16 bit platform
BYTE * pResources = (BYTE*)malloc(file.GetLength());
if(!pResources) {
file.Close();
return ERROR_NEW_FAILED;
}
file.Seek(0, CFile::begin);
file.ReadHuge(pResources, file.GetLength());
IMAGE_SECTION_HEADER Sect;
memset(&Sect, 0, sizeof(IMAGE_SECTION_HEADER));
ParseResourceFile(pResources, &Sect, (BYTE**)&lpBuffer, (LONG*)puiSize, iFileNameLen);
free(pResources);
*puiSize = uiBufSize - *puiSize;
file.Close();
return uiError;
}
file.Close();
return uiError;
}
/////////////////////////////////////////////////////////////////////////////
// We will prepend to the image the file name. This will be usefull later on
// to retrive the dialog item list
/////////////////////////////////////////////////////////////////////////////
extern "C"
DllExport
DWORD
APIENTRY
RWGetImage(
LPCSTR lpszFilename,
DWORD dwImageOffset,
LPVOID lpBuffer,
DWORD dwSize
)
{
UINT uiError = ERROR_NO_ERROR;
BYTE far * lpBuf = (BYTE far *)lpBuffer;
int iNameLen = strlen(lpszFilename)+1;
DWORD dwBufSize = dwSize - iNameLen;
// we can consider the use of a CMemFile so we get the same speed as memory access.
CFile file;
// Open the file and try to read the information on the resource in it.
if (!file.Open(lpszFilename, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone))
return (DWORD)ERROR_FILE_OPEN;
if ( dwImageOffset!=(DWORD)file.Seek( dwImageOffset, CFile::begin) )
return (DWORD)ERROR_FILE_INVALID_OFFSET;
// copy the file name at the beginning of the buffer
memcpy((BYTE*)lpBuf, lpszFilename, iNameLen);
lpBuf = ((BYTE*)lpBuf+iNameLen);
if (dwBufSize>UINT_MAX) {
// we have to read the image in different steps
return (DWORD)0L;
} else uiError = file.Read( lpBuf, (UINT)dwBufSize)+iNameLen;
file.Close();
return (DWORD)uiError;
}
extern "C"
DllExport
UINT
APIENTRY
RWParseImage(
LPCSTR lpszType,
LPVOID lpImageBuf,
DWORD dwImageSize,
LPVOID lpBuffer,
DWORD dwSize
)
{
UINT uiError = ERROR_NO_ERROR;
BYTE * lpBuf = (BYTE *)lpBuffer;
DWORD dwBufSize = dwSize;
// Remove the filename...
if( !strcmp(lpszType, "MENU") ||
!strcmp(lpszType, "STR ") ||
!strcmp(lpszType, "STR#") ||
!strcmp(lpszType, "TEXT")
) {
int iFileNameLen = strlen((LPSTR)lpImageBuf)+1;
lpImageBuf = ((BYTE*)lpImageBuf+iFileNameLen);
dwImageSize -= iFileNameLen;
}
//===============================================================
// Menus
if( !strcmp(lpszType, "MENU") )
return ParseMENU( lpImageBuf, dwImageSize, lpBuffer, dwSize );
//===============================================================
// Dialogs
if( !strcmp(lpszType, "WDLG") )
return ParseWDLG( lpImageBuf, dwImageSize, lpBuffer, dwSize );
if( !strcmp(lpszType, "DLOG") )
return ParseDLOG( lpImageBuf, dwImageSize, lpBuffer, dwSize );
if( !strcmp(lpszType, "ALRT") )
return ParseALRT( lpImageBuf, dwImageSize, lpBuffer, dwSize );
//===============================================================
// Strings
if( !strcmp(lpszType, "STR ") )
return ParseSTR( lpImageBuf, dwImageSize, lpBuffer, dwSize );
if( !strcmp(lpszType, "STR#") )
return ParseSTRNUM( lpImageBuf, dwImageSize, lpBuffer, dwSize );
if( !strcmp(lpszType, "TEXT") )
return ParseTEXT( lpImageBuf, dwImageSize, lpBuffer, dwSize );
if( !strcmp(lpszType, "WIND") )
return ParseWIND( lpImageBuf, dwImageSize, lpBuffer, dwSize );
return uiError;
}
extern"C"
DllExport
UINT
APIENTRY
RWWriteFile(
LPCSTR lpszSrcFilename,
LPCSTR lpszTgtFilename,
HANDLE hResFileModule,
LPVOID lpBuffer,
UINT uiSize,
HINSTANCE hDllInst,
LPCSTR lpszSymbolPath
)
{
TRACE("RWMAC.DLL: Source: %s\t Target: %s\n", lpszSrcFilename, lpszTgtFilename);
UINT uiError = ERROR_NO_ERROR;
PUPDATEDRESLIST pUpdList = LPNULL;
// Get the handle to the IODLL
if(InitIODLLLink())
hDllInst = g_IODLLInst;
else return ERROR_DLL_LOAD;
CFile fileIn;
CFile fileOut;
if (!fileIn.Open(lpszSrcFilename, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone))
return (DWORD)ERROR_FILE_OPEN;
if (!fileOut.Open(lpszTgtFilename, CFile::modeWrite | CFile::typeBinary))
return (DWORD)ERROR_FILE_OPEN;
MACRESHEADER fileHeader;
// Read the header of the file...
fileIn.Read(&fileHeader, sizeof(MACRESHEADER));
// allocate a buffer to hold the new resource map
// The buffer will be as big as the other one since there is no
// need, for now, to support the adding of resource
LONG lMapSize = MacLongToLong(fileHeader.mulSizeOfResMap);
BYTE * pNewMap = (BYTE*)malloc(lMapSize);
if(!pNewMap) {
uiError = ERROR_NEW_FAILED;
goto exit;
}
{ // This is for the goto. Check error:C2362
PUPDATEDRESLIST pListItem = LPNULL;
// Create the list of update resource
pUpdList = UpdatedResList( lpBuffer, uiSize );
// set the map buffer to 0 ...
memset(pNewMap, 0, lMapSize);
////////////////////////////////////////////////////////////////////////////////
// Read each resource from the resource map and check if the resource has been
// updated. If it has been updated, get the new resource image. Otherwise use
// the original resource data.
// Write the resource data in the Tgt file and write the info on the offset etc.
// in the pNewMap buffer so, when all the resources have been read and written
// the only thing left is to fix up some sizes and to write the buffer to disk.
////////////////////////////////////////////////////////////////////////////////
// Write the resource header in the Tgt file
fileOut.Write(&fileHeader, sizeof(MACRESHEADER));
BYTE * pByte = (BYTE*)malloc(256);
if(!pByte) {
uiError = ERROR_NEW_FAILED;
goto exit;
}
// Copy the Reserved and user data
fileIn.Read(pByte, 240);
fileOut.Write(pByte, 240);
free(pByte);
// store the position of the beginning of the res data
DWORD dwBeginOfResData = fileOut.GetPosition();
MACRESMAP resMap;
// Read the resource map ...
fileIn.Seek(MacLongToLong(fileHeader.mulOffsetToResMap), CFile::begin);
fileIn.Read(&resMap, sizeof(MACRESMAP));
BYTE * pTypeList = pNewMap+28;
BYTE * pTypeInfo = pTypeList+2;
BYTE * pRefList = LPNULL;
BYTE * pNameList = LPNULL;
BYTE * pName = LPNULL;
DWORD dwOffsetToTypeList = fileIn.GetPosition();
WORD wType;
fileIn.Read(&wType, sizeof(WORD));
memcpy( pNewMap+sizeof(MACRESMAP), &wType, sizeof(WORD)); // number of types - 1
wType = MacWordToWord((BYTE*)&wType)+1;
MACRESTYPELIST TypeList;
MACRESREFLIST RefList;
WORD wOffsetToRefList = wType*sizeof(MACRESTYPELIST)+sizeof(WORD);
DWORD dwOffsetToLastTypeInfo = 0;
DWORD dwOffsetToLastRefList = 0;
DWORD dwOffsetToNameList = MacLongToLong(fileHeader.mulOffsetToResMap)+MacWordToWord(resMap.mwOffsetToNameList);
DWORD dwSizeOfData = 0;
while(wType) {
// Read the type info ...
fileIn.Read(&TypeList, sizeof(MACRESTYPELIST));
dwOffsetToLastTypeInfo = fileIn.GetPosition();
// ... and update the newmap buffer
memcpy( pTypeInfo, &TypeList, sizeof(MACRESTYPELIST));
// Fix up the offset to the ref list
memcpy(((PMACRESTYPELIST)pTypeInfo)->mwOffsetToRefList, WordToMacWord(wOffsetToRefList), sizeof(WORD));
pRefList = pTypeList+wOffsetToRefList;
pTypeInfo = pTypeInfo+sizeof(MACRESTYPELIST);
// go to the refence list ...
fileIn.Seek(dwOffsetToTypeList+MacWordToWord(TypeList.mwOffsetToRefList), CFile::begin);
WORD wItems = MacWordToWord(TypeList.mwNumOfThisType)+1;
while(wItems){
// and read the reference list for this type
fileIn.Read( &RefList, sizeof(MACRESREFLIST));
dwOffsetToLastRefList = fileIn.GetPosition();
// is this a named resource ...
if(MacWordToWord(RefList.mwOffsetToResName)!=0xffff) {
// read the string
fileIn.Seek(dwOffsetToNameList+MacWordToWord(RefList.mwOffsetToResName), CFile::begin);
BYTE bLen = 0;
fileIn.Read(&bLen, 1);
if(!pNameList) {
pName = pNameList = (BYTE*)malloc(1024);
if(!pNameList) {
uiError = ERROR_NEW_FAILED;
goto exit;
}
}
// check the free space we have
if((1024-((pName-pNameList)%1024))<=bLen+1){
BYTE * pNew = (BYTE*)realloc(pNameList, _msize(pNameList)+1024);
if(!pNew) {
uiError = ERROR_NEW_FAILED;
goto exit;
}
pName = pNew+(pName-pNameList);
pNameList = pNew;
}
// Update the pointer to the string
memcpy(RefList.mwOffsetToResName, WordToMacWord((WORD)(pName-pNameList)), 2);
memcpy(pName++, &bLen, 1);
// we have room for the string
fileIn.Read(pName, bLen);
pName = pName+bLen;
}
// check if this item has been updated
if(pListItem = IsResUpdated(&TypeList.szResName[0], RefList, pUpdList)) {
// Save the offset to the resource
DWORD dwOffsetToData = fileOut.GetPosition();
DWORD dwSize = *pListItem->pSize;
// allocate the buffer to hold the resource data
pByte = (BYTE*)malloc(dwSize);
if(!pByte){
uiError = ERROR_NEW_FAILED;
goto exit;
}
// get the data from the iodll
LPSTR lpType = LPNULL;
LPSTR lpRes = LPNULL;
if (*pListItem->pTypeId) {
lpType = (LPSTR)((WORD)*pListItem->pTypeId);
} else {
lpType = (LPSTR)pListItem->pTypeName;
}
if (*pListItem->pResId) {
lpRes = (LPSTR)((WORD)*pListItem->pResId);
} else {
lpRes = (LPSTR)pListItem->pResName;
}
DWORD dwImageBufSize = (*g_lpfnGetImage)( hResFileModule,
lpType,
lpRes,
*pListItem->pLang,
pByte,
*pListItem->pSize
);
// Remove the file name from the image
int iFileNameLen = strlen((LPSTR)pByte)+1;
dwSize -= iFileNameLen;
// write the size of the data block
fileOut.Write(LongToMacLong(dwSize), sizeof(DWORD));
dwSizeOfData += dwSize+sizeof(DWORD);
fileOut.Write((pByte+iFileNameLen), dwSize);
free(pByte);
// fix up the offset to the resource in the ref list
memcpy(RefList.bOffsetToResData, LongToMacOffset(dwOffsetToData-dwBeginOfResData), 3);
}
else {
// Get the data from the Src file
// get to the data
fileIn.Seek(MacLongToLong(fileHeader.mulOffsetToResData)+
MacOffsetToLong(RefList.bOffsetToResData), CFile::begin);
// read the size of the data block
DWORD dwSize = 0;
fileIn.Read(&dwSize, sizeof(DWORD));
// Save the offset to the resource
DWORD dwOffsetToData = fileOut.GetPosition();
// write the size of the data block
fileOut.Write(&dwSize, sizeof(DWORD));
dwSizeOfData += sizeof(DWORD);
// allocate the buffer to hold the resource data
dwSizeOfData += dwSize = MacLongToLong((BYTE*)&dwSize);
pByte = (BYTE*)malloc(dwSize);
if(!pByte){
uiError = ERROR_NEW_FAILED;
goto exit;
}
// copy the data
fileIn.Read(pByte, dwSize);
fileOut.Write(pByte, dwSize);
free(pByte);
// fix up the offset to the resource in the ref list
memcpy(RefList.bOffsetToResData, LongToMacOffset(dwOffsetToData-dwBeginOfResData), 3);
}
// return in the right place
fileIn.Seek(dwOffsetToLastRefList, CFile::begin);
// copy this data in the new map buffer
memcpy(pRefList, &RefList, sizeof(MACRESREFLIST));
wOffsetToRefList+=sizeof(MACRESREFLIST);
// move to the new ref list
pRefList = pTypeList+wOffsetToRefList;
wItems--;
}
fileIn.Seek(dwOffsetToLastTypeInfo, CFile::begin);
wType--;
}
// copy the resource map header info
memcpy( pNewMap, &resMap, sizeof(MACRESMAP));
// copy the name list at the end of the res map
dwOffsetToNameList = 0;
if(pNameList) {
dwOffsetToNameList = (DWORD)(pRefList-pNewMap);
// copy the name list
memcpy(pRefList, pNameList, (size_t)(pName-pNameList));
free(pNameList);
}
// write the resource map
DWORD dwOffsetToResMap = fileOut.GetPosition();
fileOut.Write(pNewMap, lMapSize);
// We need to fix up the file header ...
fileOut.Seek(4, CFile::begin);
fileOut.Write(LongToMacLong(dwOffsetToResMap), sizeof(DWORD));
fileOut.Write(LongToMacLong(dwSizeOfData), sizeof(DWORD));
// ... and the resource map header
fileOut.Seek(dwOffsetToResMap+4, CFile::begin);
fileOut.Write(LongToMacLong(dwOffsetToResMap), sizeof(DWORD));
fileOut.Write(LongToMacLong(dwSizeOfData), sizeof(DWORD));
fileOut.Seek(dwOffsetToResMap+26, CFile::begin);
fileOut.Write(WordToMacWord(LOWORD(dwOffsetToNameList)), sizeof(WORD));
}
exit:
fileIn.Close();
fileOut.Close();
if(pNewMap)
free(pNewMap);
if(pUpdList)
free(pUpdList);
return (UINT)uiError;
}
extern "C"
DllExport
UINT
APIENTRY
RWUpdateImage(
LPCSTR lpszType,
LPVOID lpNewBuf,
DWORD dwNewSize,
LPVOID lpOldImage,
DWORD dwOldImageSize,
LPVOID lpNewImage,
DWORD* pdwNewImageSize
)
{
UINT uiError = ERROR_RW_NOTREADY;
//===============================================================
// Since all the Type are named in the mac at this stage we need to
// know the original name of the Type and not the Windows type.
// Use the typeID stored in the new ites buffer
LPSTR lpRealType = ((PRESITEM)lpNewBuf)->lpszTypeID;
if(!HIWORD(lpRealType)) // something is wrong if this is not valid
return uiError;
//===============================================================
// Menus
if( !strcmp(lpRealType, "MENU") )
return UpdateMENU( lpNewBuf, dwNewSize, lpOldImage, dwOldImageSize, lpNewImage, pdwNewImageSize );
//===============================================================
// Strings
if( !strcmp(lpRealType, "STR ") )
return UpdateSTR( lpNewBuf, dwNewSize, lpOldImage, dwOldImageSize, lpNewImage, pdwNewImageSize );
if( !strcmp(lpRealType, "STR#") )
return UpdateSTRNUM( lpNewBuf, dwNewSize, lpOldImage, dwOldImageSize, lpNewImage, pdwNewImageSize );
if( !strcmp(lpRealType, "WIND") )
return UpdateWIND( lpNewBuf, dwNewSize, lpOldImage, dwOldImageSize, lpNewImage, pdwNewImageSize );
//===============================================================
// Dialogs
if( !strcmp(lpRealType, "DLOG") )
return UpdateDLOG( lpNewBuf, dwNewSize, lpOldImage, dwOldImageSize, lpNewImage, pdwNewImageSize );
if( !strcmp(lpRealType, "ALRT") )
return UpdateALRT( lpNewBuf, dwNewSize, lpOldImage, dwOldImageSize, lpNewImage, pdwNewImageSize );
*pdwNewImageSize = 0L;
return uiError;
}
///////////////////////////////////////////////////////////////////////////
// Functions implementation
//=============================================================================
// MapToWindowsRes
//
// Map a Mac resource name to a Windows resource
//=============================================================================
WORD MapToWindowsRes( char * pResName )
{
if( !strcmp(pResName, "PICT") ||
!strcmp(pResName, "WBMP"))
return 2;
if( !strcmp(pResName, "MENU") ||
!strcmp(pResName, "WMNU"))
return 4;
if( !strcmp(pResName, "DLOG") ||
!strcmp(pResName, "ALRT") ||
!strcmp(pResName, "WDLG"))
return 5;
if( !strcmp(pResName, "STR "))
return STR_TYPE;
if( !strcmp(pResName, "STR#") ||
!strcmp(pResName, "TEXT"))
return MSG_TYPE;
if( !strcmp(pResName, "vers") ||
!strcmp(pResName, "VERS"))
return 16;
// For the Item list return 17. This means nothing to windows and will
// give us the flexibility to update the DITL list from the RW, without user
// input.
if( !strcmp(pResName, "DITL"))
return DITL_TYPE;
// For the Frame Window Caption mark it as type 18
if( !strcmp(pResName, "WIND"))
return WIND_TYPE;
return 0;
}
//=============================================================================
// WriteResInfo
//
// Fill the buffer to pass back to the iodll
//=============================================================================
LONG WriteResInfo(
BYTE** lplpBuffer, LONG* plBufSize,
WORD wTypeId, LPSTR lpszTypeId, BYTE bMaxTypeLen,
WORD wNameId, LPSTR lpszNameId, BYTE bMaxNameLen,
DWORD dwLang,
DWORD dwSize, DWORD dwFileOffset )
{
LONG lSize = 0;
lSize = PutWord( lplpBuffer, wTypeId, plBufSize );
lSize += PutStringA( lplpBuffer, lpszTypeId, plBufSize );
// Check if it is alligned
lSize += Allign( lplpBuffer, plBufSize, lSize);
lSize += PutWord( lplpBuffer, wNameId, plBufSize );
lSize += PutStringA( lplpBuffer, lpszNameId, plBufSize );
lSize += Allign( lplpBuffer, plBufSize, lSize);
lSize += PutDWord( lplpBuffer, dwLang, plBufSize );
lSize += PutDWord( lplpBuffer, dwSize, plBufSize );
lSize += PutDWord( lplpBuffer, dwFileOffset, plBufSize );
return (LONG)lSize;
}
BOOL InitIODLLLink()
{
if(!g_IODLLInst)
{
// Init the link with the iodll
g_IODLLInst = LoadLibrary("iodll.dll");
if(!g_IODLLInst)
return FALSE;
if((g_lpfnGetImage = (DWORD (PASCAL *)(HANDLE, LPCSTR, LPCSTR, DWORD, LPVOID, DWORD))
GetProcAddress( g_IODLLInst, "RSGetResImage" ))==NULL)
return FALSE;
if((g_lpfnHandleFromName = (HANDLE (PASCAL *)(LPCSTR))
GetProcAddress( g_IODLLInst, "RSHandleFromName" ))==NULL)
return FALSE;
if((g_lpfnUpdateResImage = (DWORD (PASCAL *)(HANDLE, LPSTR, LPSTR, DWORD, DWORD, LPVOID, DWORD))
GetProcAddress( g_IODLLInst, "RSUpdateResImage" ))==NULL)
return FALSE;
}
else {
if(g_lpfnGetImage==NULL || g_lpfnHandleFromName==NULL)
return FALSE;
}
return TRUE;
}
////////////////////////////////////////////////////////////////////////////
// DLL Specific code implementation
////////////////////////////////////////////////////////////////////////////
// Library init
////////////////////////////////////////////////////////////////////////////
// This function should be used verbatim. Any initialization or termination
// requirements should be handled in InitPackage() and ExitPackage().
//
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
// NOTE: global/static constructors have already been called!
// Extension DLL one-time initialization - do not allocate memory
// here, use the TRACE or ASSERT macros or call MessageBox
AfxInitExtensionModule(extensionDLL, hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
// Terminate the library before destructors are called
AfxWinTerm();
// remove the link with iodll
if(g_IODLLInst)
FreeLibrary(g_IODLLInst);
}
if (dwReason == DLL_PROCESS_DETACH || dwReason == DLL_THREAD_DETACH)
return 0; // CRT term Failed
return 1; // ok
}
/////////////////////////////////////////////////////////////////////////////