2f13e2b78df03c6967fb1178e35df9cdcfb55468
[platform/upstream/coreclr.git] / src / vm / assemblynativeresource.cpp
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
4 //
5 ////////////////////////////////////////////////////////////////////////////////
6 // ResFile.CPP
7
8
9
10 #include "common.h"
11
12 #include "assemblynativeresource.h"
13 #include <limits.h>
14
15 #ifndef CP_WINUNICODE
16  #define CP_WINUNICODE   1200
17 #endif
18
19 #ifndef MAKEINTRESOURCE
20  #define MAKEINTRESOURCE MAKEINTRESOURCEW
21 #endif
22
23 Win32Res::Win32Res()
24 {
25     CONTRACTL
26     {
27         NOTHROW;
28         GC_NOTRIGGER;
29         FORBID_FAULT;
30     }
31     CONTRACTL_END
32
33     m_szFile = NULL;
34     m_Icon = NULL;
35     int i;
36     for (i = 0; i < NUM_VALUES; i++)
37         m_Values[i] = NULL;
38     for (i = 0; i < NUM_VALUES; i++)
39         m_Values[i] = NULL;
40     m_fDll = false;
41     m_pData = NULL;
42     m_pCur = NULL;
43     m_pEnd = NULL;
44 }
45
46 Win32Res::~Win32Res()
47 {
48     CONTRACTL
49     {
50         NOTHROW;
51         GC_NOTRIGGER;
52         FORBID_FAULT;
53     }
54     CONTRACTL_END
55
56     m_szFile = NULL;
57     m_Icon = NULL;
58     int i;
59     for (i = 0; i < NUM_VALUES; i++)
60         m_Values[i] = NULL;
61     for (i = 0; i < NUM_VALUES; i++)
62         m_Values[i] = NULL;
63     m_fDll = false;
64     if (m_pData)
65         delete [] m_pData;
66     m_pData = NULL;
67     m_pCur = NULL;
68
69     m_pEnd = NULL;
70 }
71
72 //*****************************************************************************
73 // Initializes the structures with version information.
74 //*****************************************************************************
75 VOID Win32Res::SetInfo(
76     LPCWSTR     szFile, 
77     LPCWSTR     szTitle, 
78     LPCWSTR     szIconName, 
79     LPCWSTR     szDescription,
80     LPCWSTR     szCopyright, 
81     LPCWSTR     szTrademark, 
82     LPCWSTR     szCompany, 
83     LPCWSTR     szProduct, 
84     LPCWSTR     szProductVersion,
85     LPCWSTR     szFileVersion, 
86     LCID        lcid, 
87     BOOL        fDLL)
88 {
89     STANDARD_VM_CONTRACT;
90
91     _ASSERTE(szFile != NULL);
92
93     m_szFile = szFile;
94     if (szIconName && szIconName[0] != 0)
95         m_Icon = szIconName;    // a non-mepty string
96
97 #define NonNull(sz) (sz == NULL || *sz == W('\0') ? W(" ") : sz)
98     m_Values[v_Description]     = NonNull(szDescription);
99     m_Values[v_Title]           = NonNull(szTitle);
100     m_Values[v_Copyright]       = NonNull(szCopyright);
101     m_Values[v_Trademark]       = NonNull(szTrademark);
102     m_Values[v_Product]         = NonNull(szProduct);
103     m_Values[v_ProductVersion]  = NonNull(szProductVersion);
104     m_Values[v_Company]         = NonNull(szCompany);
105     m_Values[v_FileVersion]     = NonNull(szFileVersion);
106 #undef NonNull
107
108     m_fDll = fDLL;
109     m_lcid = lcid;
110 }
111
112 VOID Win32Res::MakeResFile(const void **pData, DWORD  *pcbData)
113 {
114     STANDARD_VM_CONTRACT;
115
116     static const RESOURCEHEADER magic = { 0x00000000, 0x00000020, 0xFFFF, 0x0000, 0xFFFF, 0x0000,
117                         0x00000000, 0x0000, 0x0000, 0x00000000, 0x00000000 };
118     _ASSERTE(pData != NULL && pcbData != NULL);
119
120     *pData = NULL;
121     *pcbData = 0;
122     m_pData = new BYTE[(sizeof(RESOURCEHEADER) * 3 + sizeof(EXEVERRESOURCE))];
123
124     m_pCur = m_pData;
125     m_pEnd = m_pData + sizeof(RESOURCEHEADER) * 3 + sizeof(EXEVERRESOURCE);
126
127     // inject the magic empty entry
128     Write( &magic, sizeof(magic) );
129
130     WriteVerResource();
131
132     if (m_Icon)
133     {
134         WriteIconResource();
135     }
136
137     *pData = m_pData;
138     *pcbData = (DWORD)(m_pCur - m_pData);
139     return;
140 }
141
142
143 /*
144  * WriteIconResource
145  *   Writes the Icon resource into the RES file.
146  *
147  * RETURNS: TRUE on succes, FALSE on failure (errors reported to user)
148  */
149 VOID Win32Res::WriteIconResource()
150 {
151     STANDARD_VM_CONTRACT;
152
153     HandleHolder hIconFile = INVALID_HANDLE_VALUE;
154     WORD wTemp, wCount, resID = 2;  // Skip 1 for the version ID
155     DWORD dwRead = 0, dwWritten = 0;
156
157     RESOURCEHEADER grpHeader = { 0x00000000, 0x00000020, 0xFFFF, (WORD)(size_t)RT_GROUP_ICON, 0xFFFF, 0x7F00, // 0x7F00 == IDI_APPLICATION
158                 0x00000000, 0x1030, 0x0000, 0x00000000, 0x00000000 };
159
160     // Read the icon
161     hIconFile = WszCreateFile( m_Icon, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
162         FILE_FLAG_SEQUENTIAL_SCAN, NULL);
163     if (hIconFile == INVALID_HANDLE_VALUE) {
164         COMPlusThrowWin32();
165     }
166
167     // Read the magic reserved WORD
168     if (ReadFile( hIconFile, &wTemp, sizeof(WORD), &dwRead, NULL) == FALSE) {
169         COMPlusThrowWin32();
170     } else if (wTemp != 0 || dwRead != sizeof(WORD)) {
171         COMPlusThrowHR(HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
172     }
173
174     // Verify the Type WORD
175     if (ReadFile( hIconFile, &wCount, sizeof(WORD), &dwRead, NULL) == FALSE) {
176         COMPlusThrowWin32();
177     } else if (wCount != 1 || dwRead != sizeof(WORD)) {
178         COMPlusThrowHR(HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
179     }
180
181     // Read the Count WORD
182     if (ReadFile( hIconFile, &wCount, sizeof(WORD), &dwRead, NULL) == FALSE) {
183         COMPlusThrowWin32();
184     } else if (wCount == 0 || dwRead != sizeof(WORD)) {
185         COMPlusThrowHR(HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
186     }
187
188     NewArrayHolder<ICONRESDIR> grp = new ICONRESDIR[wCount];
189     grpHeader.DataSize = 3 * sizeof(WORD) + wCount * sizeof(ICONRESDIR);
190
191     // For each Icon
192     for (WORD i = 0; i < wCount; i++) {
193         ICONDIRENTRY ico;
194         DWORD        icoPos, newPos;
195         RESOURCEHEADER icoHeader = { 0x00000000, 0x00000020, 0xFFFF, (WORD)(size_t)RT_ICON, 0xFFFF, 0x0000,
196                     0x00000000, 0x1010, 0x0000, 0x00000000, 0x00000000 };
197         icoHeader.Name = resID++;
198
199         // Read the Icon header
200         if (ReadFile( hIconFile, &ico, sizeof(ICONDIRENTRY), &dwRead, NULL) == FALSE) {
201             COMPlusThrowWin32();
202         }
203         else if (dwRead != sizeof(ICONDIRENTRY)) {
204             COMPlusThrowHR(HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
205         }
206
207         _ASSERTE(sizeof(ICONRESDIR) + sizeof(WORD) == sizeof(ICONDIRENTRY));
208         memcpy(grp + i, &ico, sizeof(ICONRESDIR));
209         grp[i].IconId = icoHeader.Name;
210         icoHeader.DataSize = ico.dwBytesInRes;
211
212         NewArrayHolder<BYTE> icoBuffer = new BYTE[icoHeader.DataSize];
213
214         // Write the header to the RES file
215         Write( &icoHeader, sizeof(RESOURCEHEADER) );
216
217         // Position to read the Icon data
218         icoPos = SetFilePointer( hIconFile, 0, NULL, FILE_CURRENT);
219         if (icoPos == INVALID_SET_FILE_POINTER) {
220             COMPlusThrowWin32();
221         }
222         newPos = SetFilePointer( hIconFile, ico.dwImageOffset, NULL, FILE_BEGIN);
223         if (newPos == INVALID_SET_FILE_POINTER) {
224             COMPlusThrowWin32();
225         }
226
227         // Actually read the data
228         if (ReadFile( hIconFile, icoBuffer, icoHeader.DataSize, &dwRead, NULL) == FALSE) {
229             COMPlusThrowWin32();
230         }
231         else if (dwRead != icoHeader.DataSize) {
232             COMPlusThrowHR(HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
233         }
234
235         // Because Icon files don't seem to record the actual Planes and BitCount in 
236         // the ICONDIRENTRY, get the info from the BITMAPINFOHEADER at the beginning
237         // of the data here:
238         grp[i].Planes = ((BITMAPINFOHEADER*)(BYTE*)icoBuffer)->biPlanes;
239         grp[i].BitCount = ((BITMAPINFOHEADER*)(BYTE*)icoBuffer)->biBitCount;
240
241         // Now write the data to the RES file
242         Write( (BYTE*)icoBuffer, icoHeader.DataSize );
243         
244         // Reposition to read the next Icon header
245         newPos = SetFilePointer( hIconFile, icoPos, NULL, FILE_BEGIN);
246         if (newPos != icoPos) {
247             COMPlusThrowWin32();
248         }
249     }
250
251     // inject the icon group
252     Write( &grpHeader, sizeof(RESOURCEHEADER) );
253
254     // Write the header to the RES file
255     wTemp = 0; // the reserved WORD
256     Write( &wTemp, sizeof(WORD) );
257
258     wTemp = RES_ICON; // the GROUP type
259     Write( &wTemp, sizeof(WORD) );
260
261     Write( &wCount, sizeof(WORD) );
262
263     // now write the entries
264     Write( grp, sizeof(ICONRESDIR) * wCount );
265
266     return;
267 }
268
269 /*
270  * WriteVerResource
271  *   Writes the version resource into the RES file.
272  *
273  * RETURNS: TRUE on succes, FALSE on failure (errors reported to user)
274  */
275 VOID Win32Res::WriteVerResource()
276 {
277     STANDARD_VM_CONTRACT;
278
279     WCHAR szLangCp[9];           // language/codepage string.
280     EXEVERRESOURCE VerResource;
281     WORD  cbStringBlocks;
282     int i;
283     bool bUseFileVer = false;
284     WCHAR       rcFile[_MAX_PATH];              // Name of file without path
285     WCHAR       rcFileExtension[_MAX_PATH];     // file extension
286     WCHAR       rcFileName[_MAX_PATH];          // Name of file with extension but without path
287     DWORD       cbTmp;
288
289     SplitPath(m_szFile, 0, 0, 0, 0, rcFile, _MAX_PATH, rcFileExtension, _MAX_PATH);
290
291     wcscpy_s(rcFileName, COUNTOF(rcFileName), rcFile);
292     wcscat_s(rcFileName, COUNTOF(rcFileName), rcFileExtension);
293
294     static const EXEVERRESOURCE VerResourceTemplate = {
295         sizeof(EXEVERRESOURCE), sizeof(VS_FIXEDFILEINFO), 0, W("VS_VERSION_INFO"),
296         {
297             VS_FFI_SIGNATURE,           // Signature
298             VS_FFI_STRUCVERSION,        // structure version
299             0, 0,                       // file version number
300             0, 0,                       // product version number
301             VS_FFI_FILEFLAGSMASK,       // file flags mask
302             0,                          // file flags
303             VOS__WINDOWS32,
304             VFT_APP,                    // file type
305             0,                          // subtype
306             0, 0                        // file date/time
307         },
308         sizeof(WORD) * 2 + 2 * HDRSIZE + KEYBYTES("VarFileInfo") + KEYBYTES("Translation"),
309         0,
310         1,
311         W("VarFileInfo"),
312         sizeof(WORD) * 2 + HDRSIZE + KEYBYTES("Translation"),
313         sizeof(WORD) * 2,
314         0,
315         W("Translation"),
316         0,
317         0,
318         2 * HDRSIZE + KEYBYTES("StringFileInfo") + KEYBYTES("12345678"),
319         0,
320         1,
321         W("StringFileInfo"),
322         HDRSIZE + KEYBYTES("12345678"),
323         0,
324         1,
325         W("12345678")
326     };
327     static const WCHAR szComments[] = W("Comments");
328     static const WCHAR szCompanyName[] = W("CompanyName");
329     static const WCHAR szFileDescription[] = W("FileDescription");
330     static const WCHAR szCopyright[] = W("LegalCopyright");
331     static const WCHAR szTrademark[] = W("LegalTrademarks");
332     static const WCHAR szProdName[] = W("ProductName");
333     static const WCHAR szFileVerResName[] = W("FileVersion");
334     static const WCHAR szProdVerResName[] = W("ProductVersion");
335     static const WCHAR szInternalNameResName[] = W("InternalName");
336     static const WCHAR szOriginalNameResName[] = W("OriginalFilename");
337     
338     // If there's no product version, use the file version
339     if (m_Values[v_ProductVersion][0] == 0) {
340         m_Values[v_ProductVersion] = m_Values[v_FileVersion];
341         bUseFileVer = true;
342     }
343
344     // Keep the two following arrays in the same order
345 #define MAX_KEY     10
346     static const LPCWSTR szKeys [MAX_KEY] = {
347         szComments,
348         szCompanyName,
349         szFileDescription,
350         szFileVerResName,
351         szInternalNameResName,
352         szCopyright,
353         szTrademark,
354         szOriginalNameResName,
355         szProdName,
356         szProdVerResName,
357     };
358     LPCWSTR szValues [MAX_KEY] = {  // values for keys
359         m_Values[v_Description],    //compiler->assemblyDescription == NULL ? W("") : compiler->assemblyDescription,
360         m_Values[v_Company],        // Company Name
361         m_Values[v_Title],          // FileDescription  //compiler->assemblyTitle == NULL ? W("") : compiler->assemblyTitle,
362         m_Values[v_FileVersion],    // FileVersion
363         rcFileName,                 // InternalName
364         m_Values[v_Copyright],      // Copyright
365         m_Values[v_Trademark],      // Trademark
366         rcFileName,                 // OriginalName
367         m_Values[v_Product],        // Product Name     //compiler->assemblyTitle == NULL ? W("") : compiler->assemblyTitle,
368         m_Values[v_ProductVersion]  // Product Version
369     };
370
371     memcpy(&VerResource, &VerResourceTemplate, sizeof(VerResource));
372
373     if (m_fDll)
374         VerResource.vsFixed.dwFileType = VFT_DLL;
375     else
376         VerResource.vsFixed.dwFileType = VFT_APP;
377
378     // Extract the numeric version from the string.
379     m_Version[0] = m_Version[1] = m_Version[2] = m_Version[3] = 0;
380     int nNumStrings = swscanf_s(m_Values[v_FileVersion], W("%hu.%hu.%hu.%hu"), m_Version, m_Version + 1, m_Version + 2, m_Version + 3);
381
382     // Fill in the FIXEDFILEINFO
383     VerResource.vsFixed.dwFileVersionMS =
384         ((DWORD)m_Version[0] << 16) + m_Version[1];
385
386     VerResource.vsFixed.dwFileVersionLS =
387         ((DWORD)m_Version[2] << 16) + m_Version[3];
388
389     if (bUseFileVer) {
390         VerResource.vsFixed.dwProductVersionLS = VerResource.vsFixed.dwFileVersionLS;
391         VerResource.vsFixed.dwProductVersionMS = VerResource.vsFixed.dwFileVersionMS;
392     }
393     else {
394         WORD v[4];
395         v[0] = v[1] = v[2] = v[3] = 0;
396         // Try to get the version numbers, but don't waste time or give any errors
397         // just default to zeros
398         nNumStrings = swscanf_s(m_Values[v_ProductVersion], W("%hu.%hu.%hu.%hu"), v, v + 1, v + 2, v + 3);
399
400         VerResource.vsFixed.dwProductVersionMS =
401             ((DWORD)v[0] << 16) + v[1];
402
403         VerResource.vsFixed.dwProductVersionLS =
404             ((DWORD)v[2] << 16) + v[3];
405     }
406
407     // There is no documentation on what units to use for the date!  So we use zero.
408     // The Windows resource compiler does too.
409     VerResource.vsFixed.dwFileDateMS = VerResource.vsFixed.dwFileDateLS = 0;
410
411     // Fill in codepage/language -- we'll assume the IDE language/codepage
412     // is the right one.
413     if (m_lcid != -1)
414         VerResource.langid = static_cast<WORD>(m_lcid);
415     else 
416         VerResource.langid = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); 
417     VerResource.codepage = CP_WINUNICODE;   // Unicode codepage.
418
419     swprintf_s(szLangCp, NumItems(szLangCp), W("%04x%04x"), VerResource.langid, VerResource.codepage);
420     wcscpy_s(VerResource.szLangCpKey, COUNTOF(VerResource.szLangCpKey), szLangCp);
421
422     // Determine the size of all the string blocks.
423     cbStringBlocks = 0;
424     for (i = 0; i < MAX_KEY; i++) {
425         if (szValues[i] == NULL || wcslen(szValues[i]) == 0)
426             continue;
427         cbTmp = SizeofVerString( szKeys[i], szValues[i]);
428         if ((cbStringBlocks + cbTmp) > USHRT_MAX / 2)
429             COMPlusThrow(kArgumentException, W("Argument_VerStringTooLong"));
430         cbStringBlocks += (WORD) cbTmp;
431     }
432
433     if ((cbStringBlocks + VerResource.cbLangCpBlock) > USHRT_MAX / 2)
434         COMPlusThrow(kArgumentException, W("Argument_VerStringTooLong"));
435     VerResource.cbLangCpBlock += cbStringBlocks;
436
437     if ((cbStringBlocks + VerResource.cbStringBlock) > USHRT_MAX / 2)
438         COMPlusThrow(kArgumentException, W("Argument_VerStringTooLong"));
439     VerResource.cbStringBlock += cbStringBlocks;
440
441     if ((cbStringBlocks + VerResource.cbRootBlock) > USHRT_MAX / 2)
442         COMPlusThrow(kArgumentException, W("Argument_VerStringTooLong"));
443     VerResource.cbRootBlock += cbStringBlocks;
444
445     // Call this VS_VERSION_INFO
446     RESOURCEHEADER verHeader = { 0x00000000, 0x0000003C, 0xFFFF, (WORD)(size_t)RT_VERSION, 0xFFFF, 0x0001,
447                                  0x00000000, 0x0030, 0x0000, 0x00000000, 0x00000000 };
448     verHeader.DataSize = VerResource.cbRootBlock;
449
450     // Write the header
451     Write( &verHeader, sizeof(RESOURCEHEADER) );
452
453     // Write the version resource
454     Write( &VerResource, sizeof(VerResource) );
455     
456
457     // Write each string block.
458     for (i = 0; i < MAX_KEY; i++) {
459         if (szValues[i] == NULL || wcslen(szValues[i]) == 0)
460             continue;
461         WriteVerString( szKeys[i], szValues[i] );
462     }
463 #undef MAX_KEY
464
465     return;
466 }
467
468 /*
469  * SizeofVerString
470  *    Determines the size of a version string to the given stream.
471  * RETURNS: size of block in bytes.
472  */
473 WORD Win32Res::SizeofVerString(LPCWSTR lpszKey, LPCWSTR lpszValue)
474 {
475     STANDARD_VM_CONTRACT;
476
477     size_t cbKey, cbValue;
478
479     cbKey = (wcslen(lpszKey) + 1) * 2;  // Make room for the NULL
480     cbValue = (wcslen(lpszValue) + 1) * 2;
481     if (cbValue == 2)
482         cbValue = 4;   // Empty strings need a space and NULL terminator (for Win9x)
483     if (cbKey + cbValue >= 0xFFF0)
484         COMPlusThrow(kArgumentException, W("Argument_VerStringTooLong"));
485
486 #ifdef _PREFAST_
487 #pragma warning(push)
488 #pragma warning(disable:6305) // "Potential mismatch between sizeof and countof quantities"
489 #endif
490
491     return (WORD)(PadKeyLen(cbKey) +   // key, 0 padded to DWORD boundary
492                   PadValLen(cbValue) + // value, 0 padded to dword boundary
493                   HDRSIZE);             // block header.
494
495 #ifdef _PREFAST_
496 #pragma warning(pop)
497 #endif
498 }
499
500 /*----------------------------------------------------------------------------
501  * WriteVerString
502  *    Writes a version string to the given file.
503  */
504 VOID Win32Res::WriteVerString( LPCWSTR lpszKey, LPCWSTR lpszValue)
505 {
506     STANDARD_VM_CONTRACT;
507
508     size_t cbKey, cbValue, cbBlock;
509     bool bNeedsSpace = false;
510
511     cbKey = (wcslen(lpszKey) + 1) * 2;     // includes terminating NUL
512     cbValue = wcslen(lpszValue);
513     if (cbValue > 0)
514         cbValue++; // make room for NULL
515     else {
516         bNeedsSpace = true;
517         cbValue = 2; // Make room for space and NULL (for Win9x)
518     }
519     cbBlock = SizeofVerString(lpszKey, lpszValue);
520
521     NewArrayHolder<BYTE> pbBlock = new BYTE[(DWORD)cbBlock + HDRSIZE];
522     ZeroMemory(pbBlock, (DWORD)cbBlock + HDRSIZE);
523
524     _ASSERTE(cbValue < USHRT_MAX && cbKey < USHRT_MAX && cbBlock < USHRT_MAX);
525
526     // Copy header, key and value to block.
527     *(WORD *)((BYTE *)pbBlock) = (WORD)cbBlock;
528     *(WORD *)(pbBlock + sizeof(WORD)) = (WORD)cbValue;
529     *(WORD *)(pbBlock + 2 * sizeof(WORD)) = 1;   // 1 = text value
530     // size = (cbBlock + HDRSIZE - HDRSIZE) / sizeof(WCHAR)
531     wcscpy_s((WCHAR*)(pbBlock + HDRSIZE), (cbBlock / sizeof(WCHAR)), lpszKey);
532
533 #ifdef _PREFAST_
534 #pragma warning(push)
535 #pragma warning(disable:6305) // "Potential mismatch between sizeof and countof quantities"
536 #endif
537
538     if (bNeedsSpace)
539         *((WCHAR*)(pbBlock + (HDRSIZE + PadKeyLen(cbKey)))) = W(' ');
540     else
541     {
542         wcscpy_s((WCHAR*)(pbBlock + (HDRSIZE + PadKeyLen(cbKey))),
543                  //size = ((cbBlock + HDRSIZE) - (HDRSIZE + PadKeyLen(cbKey))) / sizeof(WCHAR)
544                  (cbBlock - PadKeyLen(cbKey))/sizeof(WCHAR),
545                  lpszValue);
546     }
547
548 #ifdef _PREFAST_
549 #pragma warning(pop)
550 #endif
551
552     // Write block
553     Write( pbBlock, cbBlock);
554
555     return;
556 }
557
558 VOID Win32Res::Write(LPCVOID pData, size_t len)
559 {
560     STANDARD_VM_CONTRACT;
561
562     if (m_pCur + len > m_pEnd) {
563         // Grow
564         size_t newSize = (m_pEnd - m_pData);
565
566         // double the size unless we need more than that
567         if (len > newSize)
568             newSize += len;
569         else
570             newSize *= 2;
571
572         LPBYTE pNew = new BYTE[newSize];
573         memcpy(pNew, m_pData, m_pCur - m_pData);
574         delete [] m_pData;
575         // Relocate the pointers
576         m_pCur = pNew + (m_pCur - m_pData);
577         m_pData = pNew;
578         m_pEnd = pNew + newSize;
579     }
580
581     // Copy it in
582     memcpy(m_pCur, pData, len);
583     m_pCur += len;
584     return;
585 }
586