Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

711 lines
19 KiB

  1. //**************************************************************************
  2. //
  3. // Copyright (C) Microsoft Corporation, 1998 - 1999 All Rights Reserved.
  4. //
  5. // File: frmsave.cpp
  6. //
  7. // Description: Save LPDIRECT3DRMFRAME to an x file.
  8. //
  9. // History:
  10. // 011/06/98 CongpaY Created
  11. //
  12. //**************************************************************************
  13. #include <d3drm.h>
  14. #include <dxfile.h>
  15. #include <rmxftmpl.h>
  16. #include <rmxfguid.h>
  17. #include "frmsave.h"
  18. extern HINSTANCE g_hInstD3DXOFDLL;
  19. #define MyD3DRMColorGetAlpha(color) ((float)((color & 0xFF000000)>>24)/(float)255)
  20. #define MyD3DRMColorGetRed(color) ((float)((color & 0x00FF0000)>>16)/(float)255)
  21. #define MyD3DRMColorGetGreen(color) ((float)((color & 0x0000FF00)>>8)/(float)255)
  22. #define MyD3DRMColorGetBlue(color) ((float)((color & 0x000000FF))/(float)255)
  23. HRESULT FrameToXFile(LPDIRECT3DRMFRAME3 pFrame,
  24. LPCSTR filename,
  25. D3DRMXOFFORMAT d3dFormat,
  26. D3DRMSAVEOPTIONS d3dSaveFlags)
  27. {
  28. Saver saver;
  29. saver.Init(filename, d3dFormat, d3dSaveFlags);
  30. saver.SaveHeaderObject();
  31. saver.SaveFrame(pFrame);
  32. return S_OK;
  33. }
  34. Saver::Saver()
  35. : pXFile(NULL),
  36. pSave(NULL)
  37. {
  38. }
  39. Saver::~Saver()
  40. {
  41. if (pSave) pSave->Release();
  42. if (pXFile) pXFile->Release();
  43. }
  44. HRESULT Saver::Init(LPCSTR filename,
  45. D3DRMXOFFORMAT d3dFormatArg,
  46. D3DRMSAVEOPTIONS d3dSaveFlagsArg)
  47. {
  48. d3dFormat = d3dFormatArg;
  49. d3dSaveFlags = d3dSaveFlagsArg;
  50. CREATEXFILE pCreateXFile=(CREATEXFILE)GetProcAddress( g_hInstD3DXOFDLL, "DirectXFileCreate" );
  51. if (!pCreateXFile) return E_NOTIMPL;
  52. DXFILEFORMAT xFormat;
  53. if (d3dFormat == D3DRMXOF_BINARY)
  54. xFormat = DXFILEFORMAT_BINARY;
  55. else if (d3dFormat == D3DRMXOF_TEXT)
  56. xFormat = DXFILEFORMAT_TEXT;
  57. else
  58. xFormat = DXFILEFORMAT_COMPRESSED;
  59. //DirectXFileCreate(&pXFile);
  60. pCreateXFile(&pXFile);
  61. pXFile->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES);
  62. pXFile->CreateSaveObject(filename, xFormat, &pSave);
  63. return S_OK;
  64. }
  65. HRESULT Saver::SaveHeaderObject()
  66. {
  67. LPDIRECTXFILEDATA pHeader;
  68. Header data;
  69. data.major = 1;
  70. data.minor = 0;
  71. data.flags = (d3dFormat == D3DRMXOF_TEXT)? 1 : 0;
  72. pSave->CreateDataObject(TID_DXFILEHeader,
  73. NULL,
  74. NULL,
  75. sizeof(Header),
  76. &data,
  77. &pHeader);
  78. pSave->SaveData(pHeader);
  79. pHeader->Release();
  80. return S_OK;
  81. }
  82. HRESULT Saver::SaveFrame(LPDIRECT3DRMFRAME3 pFrame,
  83. LPDIRECT3DRMFRAME3 pRefFrame,
  84. LPDIRECTXFILEDATA pRefFrameObj)
  85. {
  86. DWORD i;
  87. HRESULT hr;
  88. LPDIRECTXFILEDATA pFrameObj;
  89. pSave->CreateDataObject(TID_D3DRMFrame,
  90. NULL,
  91. NULL,
  92. 0,
  93. NULL,
  94. &pFrameObj);
  95. SaveFrameTransform(pFrameObj, pFrame, pRefFrame);
  96. // Enumerate visuals.
  97. DWORD cVisuals;
  98. pFrame->GetVisuals(&cVisuals, NULL);
  99. if (cVisuals)
  100. {
  101. LPUNKNOWN *ppUnk = new LPUNKNOWN[cVisuals];
  102. pFrame->GetVisuals(&cVisuals, ppUnk);
  103. for (i = 0; i < cVisuals; i++)
  104. {
  105. LPDIRECT3DRMFRAME3 pChildFrame;
  106. hr = ppUnk[i]->QueryInterface(IID_IDirect3DRMFrame3, (LPVOID *)&pChildFrame);
  107. if (SUCCEEDED(hr))
  108. {
  109. SaveFrame(pChildFrame, pFrame, pFrameObj);
  110. pChildFrame->Release();
  111. }
  112. else
  113. {
  114. LPDIRECT3DRMMESHBUILDER3 pMeshBuilder;
  115. hr = ppUnk[i]->QueryInterface(IID_IDirect3DRMMeshBuilder3, (LPVOID *)&pMeshBuilder);
  116. if (SUCCEEDED(hr))
  117. {
  118. SaveMeshBuilder(pFrameObj, pMeshBuilder);
  119. pMeshBuilder->Release();
  120. }
  121. }
  122. ppUnk[i]->Release();
  123. }
  124. delete[] ppUnk;
  125. }
  126. // Enumerate child frames.
  127. LPDIRECT3DRMFRAMEARRAY pFrameArray;
  128. pFrame->GetChildren(&pFrameArray);
  129. for (i = 0; i < pFrameArray->GetSize(); i++)
  130. {
  131. LPDIRECT3DRMFRAME pTmpFrame;
  132. LPDIRECT3DRMFRAME3 pChildFrame;
  133. pFrameArray->GetElement(i, &pTmpFrame);
  134. pTmpFrame->QueryInterface(IID_IDirect3DRMFrame3, (LPVOID *)&pChildFrame);
  135. pTmpFrame->Release();
  136. SaveFrame(pChildFrame, pFrame, pFrameObj);
  137. pChildFrame->Release();
  138. }
  139. pFrameArray->Release();
  140. // Add frame object to the saved list.
  141. if (pRefFrameObj)
  142. pRefFrameObj->AddDataObject(pFrameObj);
  143. else
  144. pSave->SaveData(pFrameObj);
  145. pFrameObj->Release();
  146. return S_OK;
  147. }
  148. HRESULT Saver::SaveFrameTransform(LPDIRECTXFILEDATA pFrameObj,
  149. LPDIRECT3DRMFRAME3 pFrame,
  150. LPDIRECT3DRMFRAME3 pRefFrame)
  151. {
  152. LPDIRECTXFILEDATA pFrameTransformObj;
  153. D3DRMMATRIX4D rmMatrix;
  154. pFrame->GetTransform(pRefFrame, rmMatrix);
  155. pSave->CreateDataObject(TID_D3DRMFrameTransformMatrix,
  156. NULL,
  157. NULL,
  158. sizeof(D3DRMMATRIX4D),
  159. &rmMatrix,
  160. &pFrameTransformObj);
  161. pFrameObj->AddDataObject(pFrameTransformObj);
  162. pFrameTransformObj->Release();
  163. return S_OK;
  164. }
  165. HRESULT Saver::SaveMeshBuilder(LPDIRECTXFILEDATA pFrameObj,
  166. LPDIRECT3DRMMESHBUILDER3 pMeshBuilder)
  167. {
  168. LPDIRECTXFILEDATA pMeshObj;
  169. DWORD cVertices, cNormals, cFaces, dwFaceData, *pdwFaceData;
  170. LPDIRECT3DRMFACEARRAY pFaceArray = NULL;
  171. pMeshBuilder->GetGeometry(&cVertices, NULL,
  172. &cNormals, NULL,
  173. &dwFaceData, NULL);
  174. cFaces = pMeshBuilder->GetFaceCount();
  175. if (!cVertices || !cNormals || !dwFaceData || !cFaces)
  176. return S_OK;
  177. pdwFaceData = new DWORD[dwFaceData];
  178. pMeshBuilder->GetGeometry(NULL, NULL,
  179. NULL, NULL,
  180. &dwFaceData, pdwFaceData);
  181. CreateMeshObject(cVertices, cFaces, dwFaceData, pdwFaceData,
  182. pMeshBuilder, &pMeshObj);
  183. D3DRMCOLORSOURCE clrSrc = pMeshBuilder->GetColorSource();
  184. if (clrSrc == D3DRMCOLOR_FROMVERTEX)
  185. {
  186. CreateVertexColorsObject(pMeshObj, cVertices, pMeshBuilder);
  187. }
  188. if (d3dSaveFlags & D3DRMXOFSAVE_MATERIALS)
  189. {
  190. if (!pFaceArray)
  191. pMeshBuilder->GetFaces(&pFaceArray);
  192. CreateMaterialListObject(pMeshObj, pFaceArray);
  193. }
  194. if (d3dSaveFlags & D3DRMXOFSAVE_NORMALS)
  195. {
  196. CreateNormalsObject(pMeshObj,
  197. cNormals, cFaces, dwFaceData, pdwFaceData,
  198. pMeshBuilder);
  199. }
  200. if (d3dSaveFlags & D3DRMXOFSAVE_TEXTURETOPOLOGY)
  201. {
  202. if (!pFaceArray)
  203. pMeshBuilder->GetFaces(&pFaceArray);
  204. CreateTextureWrapsObject(pMeshObj, pFaceArray);
  205. }
  206. if (d3dSaveFlags & D3DRMXOFSAVE_TEXTURECOORDINATES)
  207. {
  208. CreateTextureCoordsObject(pMeshObj, cVertices, pMeshBuilder);
  209. }
  210. if (pFrameObj)
  211. pFrameObj->AddDataObject(pMeshObj);
  212. else
  213. pSave->SaveData(pMeshObj);
  214. pMeshObj->Release();
  215. delete[] pdwFaceData;
  216. if (pFaceArray)
  217. pFaceArray->Release();
  218. return S_OK;
  219. }
  220. HRESULT Saver::CreateMeshObject(DWORD cVertices,
  221. DWORD cFaces,
  222. DWORD dwFaceData,
  223. LPDWORD pdwFaceData,
  224. LPDIRECT3DRMMESHBUILDER3 pMeshBuilder,
  225. LPDIRECTXFILEDATA *ppMeshObj)
  226. {
  227. // mesh data is vertex_count + vertices + face_count + face_vertex_data;
  228. DWORD cbSize, *data;
  229. cbSize = cVertices * sizeof(D3DVECTOR) +
  230. (1 + (dwFaceData + cFaces + 1)/2) * sizeof(DWORD);
  231. data = (LPDWORD) new BYTE[cbSize];
  232. data[0] = cVertices;
  233. LPD3DVECTOR pVertices = (LPD3DVECTOR)&data[1];
  234. pMeshBuilder->GetGeometry(&cVertices, pVertices,
  235. NULL, NULL,
  236. NULL, NULL);
  237. LPDWORD pdwTmp = (LPDWORD)&pVertices[cVertices];
  238. *pdwTmp++ = cFaces;
  239. while (*pdwFaceData)
  240. {
  241. DWORD cFaceVertices = *pdwFaceData++;
  242. *pdwTmp++ = cFaceVertices;
  243. for (DWORD i = 0; i < cFaceVertices; i++)
  244. {
  245. *pdwTmp++ = *pdwFaceData++;
  246. pdwFaceData++; // skip normal index.
  247. }
  248. }
  249. DWORD dwSize;
  250. pMeshBuilder->GetName(&dwSize, NULL);
  251. LPSTR szName = NULL;
  252. if (dwSize)
  253. {
  254. szName = new char[dwSize];
  255. pMeshBuilder->GetName(&dwSize, szName);
  256. }
  257. pSave->CreateDataObject(TID_D3DRMMesh,
  258. szName,
  259. NULL,
  260. cbSize,
  261. data,
  262. ppMeshObj);
  263. if (szName) lNames.Add(szName);
  264. delete[] data;
  265. return S_OK;
  266. }
  267. HRESULT Saver::CreateNormalsObject(LPDIRECTXFILEDATA pMeshObj,
  268. DWORD cNormals,
  269. DWORD cFaces,
  270. DWORD dwFaceData,
  271. LPDWORD pdwFaceData,
  272. LPDIRECT3DRMMESHBUILDER3 pMeshBuilder)
  273. {
  274. // normals data is normal_count + normals + face_count + face_normal_data;
  275. DWORD cbSize, *data;
  276. cbSize = cNormals * sizeof(D3DVECTOR) +
  277. (1 + (dwFaceData + cFaces + 1)/2) * sizeof(DWORD);
  278. data = (LPDWORD) new BYTE[cbSize];
  279. data[0] = cNormals;
  280. LPD3DVECTOR pNormals = (LPD3DVECTOR)&data[1];
  281. pMeshBuilder->GetGeometry(NULL, NULL,
  282. &cNormals, pNormals,
  283. NULL, NULL);
  284. LPDWORD pdwTmp = (LPDWORD)&pNormals[cNormals];
  285. *pdwTmp++ = cFaces;
  286. while (*pdwFaceData)
  287. {
  288. DWORD cFaceVertices = *pdwFaceData++;
  289. *pdwTmp++ = cFaceVertices;
  290. for (DWORD i = 0; i < cFaceVertices; i++)
  291. {
  292. pdwFaceData++; // skip vertex index.
  293. *pdwTmp++ = *pdwFaceData++;
  294. }
  295. }
  296. LPDIRECTXFILEDATA pNormalsObj;
  297. pSave->CreateDataObject(TID_D3DRMMeshNormals,
  298. NULL,
  299. NULL,
  300. cbSize,
  301. data,
  302. &pNormalsObj);
  303. pMeshObj->AddDataObject(pNormalsObj);
  304. pNormalsObj->Release();
  305. delete[] data;
  306. return S_OK;
  307. }
  308. HRESULT Saver::CreateVertexColorsObject(LPDIRECTXFILEDATA pMeshObj,
  309. DWORD cVertices,
  310. LPDIRECT3DRMMESHBUILDER3 pMeshBuilder)
  311. {
  312. DWORD cbSize;
  313. VertexColors *data;
  314. cbSize = sizeof(DWORD) + cVertices * sizeof(IndexedColor);
  315. data = (VertexColors *) new BYTE[cbSize];
  316. data->cVertices = cVertices;
  317. for (DWORD i = 0; i < cVertices; i++)
  318. {
  319. D3DCOLOR color = pMeshBuilder->GetVertexColor(i);
  320. data->vertexColors[i].index = i;
  321. data->vertexColors[i].color.r = MyD3DRMColorGetRed(color);
  322. data->vertexColors[i].color.g = MyD3DRMColorGetGreen(color);
  323. data->vertexColors[i].color.b = MyD3DRMColorGetBlue(color);
  324. data->vertexColors[i].color.a = MyD3DRMColorGetAlpha(color);
  325. }
  326. LPDIRECTXFILEDATA pVertexColorsObj;
  327. pSave->CreateDataObject(TID_D3DRMMeshVertexColors,
  328. NULL,
  329. NULL,
  330. cbSize,
  331. data,
  332. &pVertexColorsObj);
  333. pMeshObj->AddDataObject(pVertexColorsObj);
  334. pVertexColorsObj->Release();
  335. delete[] data;
  336. return S_OK;
  337. }
  338. HRESULT Saver::CreateMaterialListObject(LPDIRECTXFILEDATA pMeshObj,
  339. LPDIRECT3DRMFACEARRAY pFaceArray)
  340. {
  341. DWORD cbSize, cFaces;
  342. FaceMaterials *data;
  343. FaceMaterialList lMat;
  344. cFaces = pFaceArray->GetSize();
  345. cbSize = (2 + cFaces) * sizeof(DWORD);
  346. data = (FaceMaterials *) new BYTE[cbSize];
  347. data->cFaceIndexes = cFaces;
  348. LPDWORD pdwIndex = data->faceIndexes;
  349. for (DWORD i = 0; i < cFaces; i++, pdwIndex++)
  350. {
  351. LPDIRECT3DRMFACE pFace;
  352. pFaceArray->GetElement(i, &pFace);
  353. D3DCOLOR faceColor;
  354. LPDIRECT3DRMMATERIAL pMaterial;
  355. LPDIRECT3DRMTEXTURE pTexture;
  356. faceColor = pFace->GetColor();
  357. pFace->GetMaterial(&pMaterial);
  358. pFace->GetTexture(&pTexture);
  359. *pdwIndex = lMat.Find(faceColor, pMaterial, pTexture);
  360. pMaterial->Release();
  361. if (pTexture) pTexture->Release();
  362. pFace->Release();
  363. }
  364. data->cMaterials = lMat.Count();
  365. if (data->cMaterials == 1)
  366. {
  367. data->cFaceIndexes = 1;
  368. data->faceIndexes[0] = 0;
  369. cbSize = 3 * sizeof(DWORD);
  370. }
  371. LPDIRECTXFILEDATA pMatListObj;
  372. pSave->CreateDataObject(TID_D3DRMMeshMaterialList,
  373. NULL,
  374. NULL,
  375. cbSize,
  376. data,
  377. &pMatListObj);
  378. FaceMaterial *pMat;
  379. for (pMat = lMat.First(); pMat; pMat = pMat->pNext)
  380. {
  381. CreateMaterialObject(pMatListObj,
  382. pMat);
  383. }
  384. pMeshObj->AddDataObject(pMatListObj);
  385. pMatListObj->Release();
  386. delete[] data;
  387. return S_OK;
  388. }
  389. HRESULT Saver::CreateMaterialObject(LPDIRECTXFILEDATA pMatListObj,
  390. FaceMaterial *pMat)
  391. {
  392. BaseMaterial data;
  393. data.faceColor.r = MyD3DRMColorGetRed(pMat->faceColor);
  394. data.faceColor.g = MyD3DRMColorGetGreen(pMat->faceColor);
  395. data.faceColor.b = MyD3DRMColorGetBlue(pMat->faceColor);
  396. data.faceColor.a = MyD3DRMColorGetAlpha(pMat->faceColor);
  397. data.power = pMat->pMaterial->GetPower();
  398. pMat->pMaterial->GetSpecular(&data.specularColor.r,
  399. &data.specularColor.g,
  400. &data.specularColor.b);
  401. pMat->pMaterial->GetEmissive(&data.emissiveColor.r,
  402. &data.emissiveColor.g,
  403. &data.emissiveColor.b);
  404. LPDIRECTXFILEDATA pMaterialObj;
  405. pSave->CreateDataObject(TID_D3DRMMaterial,
  406. NULL,
  407. NULL,
  408. sizeof(BaseMaterial),
  409. &data,
  410. &pMaterialObj);
  411. if (pMat->pTexture)
  412. {
  413. IDirectXFileData *pTextureObj;
  414. DWORD dwSize;
  415. pMat->pTexture->GetName(&dwSize, NULL);
  416. if (dwSize)
  417. {
  418. LPSTR szName = new char[dwSize];
  419. pMat->pTexture->GetName(&dwSize, szName);
  420. pSave->CreateDataObject(TID_D3DRMTextureFilename,
  421. NULL,
  422. NULL,
  423. sizeof(LPSTR),
  424. &szName,
  425. &pTextureObj);
  426. pMaterialObj->AddDataObject(pTextureObj);
  427. pTextureObj->Release();
  428. lNames.Add(szName);
  429. }
  430. }
  431. pMatListObj->AddDataObject(pMaterialObj);
  432. pMaterialObj->Release();
  433. return S_OK;
  434. }
  435. HRESULT Saver::CreateTextureWrapsObject(LPDIRECTXFILEDATA pMeshObj,
  436. LPDIRECT3DRMFACEARRAY pFaceArray)
  437. {
  438. DWORD cbSize, cFaces;
  439. FaceWraps *data;
  440. cFaces = pFaceArray->GetSize();
  441. cbSize = sizeof(DWORD) + cFaces * sizeof(Boolean2d);
  442. data = (FaceWraps *) new BYTE[cbSize];
  443. data->cFaces = cFaces;
  444. Boolean2d *pWrap = data->faceWraps;
  445. for (DWORD i = 0; i < cFaces; i++, pWrap++)
  446. {
  447. LPDIRECT3DRMFACE pFace;
  448. pFaceArray->GetElement(i, &pFace);
  449. pFace->GetTextureTopology(&pWrap->u, &pWrap->v);
  450. pFace->Release();
  451. }
  452. LPDIRECTXFILEDATA pTextureWrapsObj;
  453. pSave->CreateDataObject(TID_D3DRMMeshFaceWraps,
  454. NULL,
  455. NULL,
  456. cbSize,
  457. data,
  458. &pTextureWrapsObj);
  459. pMeshObj->AddDataObject(pTextureWrapsObj);
  460. pTextureWrapsObj->Release();
  461. delete[] data;
  462. return S_OK;
  463. }
  464. HRESULT Saver::CreateTextureCoordsObject(LPDIRECTXFILEDATA pMeshObj,
  465. DWORD cVertices,
  466. LPDIRECT3DRMMESHBUILDER3 pMeshBuilder)
  467. {
  468. DWORD cbSize;
  469. TextureCoords *data;
  470. cbSize = sizeof(DWORD) + cVertices * sizeof(Coords2d);
  471. data = (TextureCoords *) new BYTE[cbSize];
  472. data->cVertices = cVertices;
  473. Coords2d *pCoords = data->textureCoords;
  474. for (DWORD i = 0; i < cVertices; i++, pCoords++)
  475. {
  476. pMeshBuilder->GetTextureCoordinates(i, &pCoords->u, &pCoords->v);
  477. }
  478. LPDIRECTXFILEDATA pTexCoordsObj;
  479. pSave->CreateDataObject(TID_D3DRMMeshTextureCoords,
  480. NULL,
  481. NULL,
  482. cbSize,
  483. data,
  484. &pTexCoordsObj);
  485. pMeshObj->AddDataObject(pTexCoordsObj);
  486. pTexCoordsObj->Release();
  487. delete[] data;
  488. return S_OK;
  489. }
  490. FaceMaterialList::FaceMaterialList()
  491. : cElements(0), pFirst(NULL)
  492. {
  493. }
  494. FaceMaterialList::~FaceMaterialList()
  495. {
  496. FaceMaterial *pMat = pFirst;
  497. while (pMat)
  498. {
  499. FaceMaterial *pNext = pMat->pNext;
  500. pMat->pMaterial->Release();
  501. if (pMat->pTexture) pMat->pTexture->Release();
  502. delete pMat;
  503. pMat = pNext;
  504. }
  505. }
  506. DWORD FaceMaterialList::Find(D3DCOLOR faceColor,
  507. LPDIRECT3DRMMATERIAL pMaterial,
  508. LPDIRECT3DRMTEXTURE pTexture)
  509. {
  510. FaceMaterial *pTmp = pFirst;
  511. FaceMaterial **ppNew = &pFirst;
  512. for (DWORD i = 0; pTmp; i++, pTmp = pTmp->pNext)
  513. {
  514. if (pTmp->faceColor == faceColor &&
  515. pTmp->pMaterial == pMaterial &&
  516. pTmp->pTexture == pTexture)
  517. return i;
  518. if (!pTmp->pNext)
  519. ppNew = &pTmp->pNext;
  520. }
  521. FaceMaterial *pNew = new FaceMaterial;
  522. pNew->faceColor = faceColor;
  523. pNew->pMaterial = pMaterial;
  524. pNew->pTexture = pTexture;
  525. pNew->pNext = NULL;
  526. pMaterial->AddRef();
  527. if (pTexture) pTexture->AddRef();
  528. *ppNew = pNew;
  529. cElements++;
  530. return i;
  531. }
  532. NameList::NameList()
  533. : pFirst(NULL),
  534. ppLast(NULL)
  535. {
  536. }
  537. NameList::~NameList()
  538. {
  539. NameEntry *pEntry = pFirst;
  540. while (pEntry)
  541. {
  542. NameEntry *pNext = pEntry->pNext;
  543. delete[] pEntry->pName;
  544. delete pEntry;
  545. pEntry = pNext;
  546. }
  547. }
  548. void NameList::Add(LPSTR pName)
  549. {
  550. NameEntry *pNew = new NameEntry;
  551. pNew->pName = pName;
  552. pNew->pNext = NULL;
  553. if (ppLast)
  554. *ppLast = pNew;
  555. else
  556. pFirst = pNew;
  557. ppLast = &pNew->pNext;
  558. }