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.

694 lines
18 KiB

  1. #include <stdio.h>
  2. #include <windows.h>
  3. #include "patchapi.h"
  4. #include "const.h"
  5. #include "ansparse.h"
  6. ///////////////////////////////////////////////////////////////////////////////
  7. //
  8. // class PATCH_LANGUAGE
  9. //
  10. ///////////////////////////////////////////////////////////////////////////////
  11. ///////////////////////////////////////////////////////////////////////////////
  12. //
  13. // PATCH_LANGUAGE, constructor for the language struct, just zero everything
  14. //
  15. ///////////////////////////////////////////////////////////////////////////////
  16. PATCH_LANGUAGE::PATCH_LANGUAGE() :
  17. s_hScriptFile(INVALID_HANDLE_VALUE),
  18. s_blnBase(FALSE),
  19. s_iComplete(0),
  20. s_iDirectoryCount(0),
  21. s_iPatchDirectoryCount(0),
  22. s_iSubPatchDirectoryCount(0),
  23. s_iSubExceptDirectoryCount(0),
  24. s_pNext(NULL)
  25. {
  26. }
  27. ///////////////////////////////////////////////////////////////////////////////
  28. //
  29. // ~PATCH_LANGUAGE, destructor, erases the structures recursively
  30. //
  31. ///////////////////////////////////////////////////////////////////////////////
  32. PATCH_LANGUAGE::~PATCH_LANGUAGE()
  33. {
  34. if(s_pNext)
  35. {
  36. delete s_pNext;
  37. s_pNext = NULL;
  38. }
  39. if(s_hScriptFile != INVALID_HANDLE_VALUE)
  40. {
  41. CloseHandle(s_hScriptFile);
  42. }
  43. }
  44. ///////////////////////////////////////////////////////////////////////////////
  45. //
  46. // class AnswerParser
  47. //
  48. ///////////////////////////////////////////////////////////////////////////////
  49. ///////////////////////////////////////////////////////////////////////////////
  50. //
  51. // AnswerParser, constructor for the parser, initializes member variables
  52. //
  53. ///////////////////////////////////////////////////////////////////////////////
  54. AnswerParser::AnswerParser() :
  55. m_hAnsFile(INVALID_HANDLE_VALUE),
  56. m_pHead(NULL),
  57. m_iBaseDirectoryCount(0)
  58. {
  59. ZeroMemory(m_wszBaseDirectory, sizeof(m_wszBaseDirectory));
  60. ZeroMemory(m_structHash, sizeof(m_structHash));
  61. ZeroMemory(m_structHashUsed, sizeof(m_structHashUsed));
  62. }
  63. ///////////////////////////////////////////////////////////////////////////////
  64. //
  65. // AnswerParser, destructor for the parser, removes the language structures
  66. //
  67. ///////////////////////////////////////////////////////////////////////////////
  68. AnswerParser::~AnswerParser()
  69. {
  70. if(m_hAnsFile != INVALID_HANDLE_VALUE)
  71. {
  72. CloseHandle(m_hAnsFile);
  73. m_hAnsFile = INVALID_HANDLE_VALUE;
  74. }
  75. if(m_pHead != NULL)
  76. {
  77. delete m_pHead;
  78. m_pHead = NULL;
  79. }
  80. }
  81. ///////////////////////////////////////////////////////////////////////////////
  82. //
  83. // GetHashValues, computes a hashvalue based on double hashing
  84. //
  85. // Parameters:
  86. //
  87. // pwszFileName, the filename which the hash values are calculated from
  88. // iHash1, the first hash value
  89. // iHash2, the second hash value
  90. //
  91. // Return:
  92. //
  93. // none
  94. //
  95. ///////////////////////////////////////////////////////////////////////////////
  96. VOID AnswerParser::GetHashValues(IN CONST WCHAR* pwszFileName,
  97. OUT ULONG& iHash1,
  98. OUT ULONG& iHash2)
  99. {
  100. ULONG iLength = wcslen(pwszFileName);
  101. if(iLength <= 2)
  102. {
  103. // minimum 1 letter filename
  104. iHash1 = ((ULONG)(pwszFileName[0])) % EXCEP_FILE_LIMIT;
  105. iHash2 = (((ULONG)(pwszFileName[0])) + 17) % EXCEP_FILE_LIMIT;
  106. }
  107. else if(iLength <= 5)
  108. {
  109. // minimum 3 letter
  110. iHash1 = ((ULONG)(pwszFileName[1])) % EXCEP_FILE_LIMIT;
  111. iHash2 = ((ULONG)(pwszFileName[2])) % EXCEP_FILE_LIMIT;
  112. }
  113. else if(iLength <= 10)
  114. {
  115. // minimum 6 letter
  116. iHash1 = (((ULONG)(pwszFileName[0])) + 1) * (((ULONG)(pwszFileName[1])) + 2) % EXCEP_FILE_LIMIT;
  117. iHash2 = (((ULONG)(pwszFileName[4])) + 5) * (((ULONG)(pwszFileName[5])) + 6) % EXCEP_FILE_LIMIT;
  118. }
  119. else if(iLength <= 15)
  120. {
  121. // minimum 11 letter
  122. iHash1 = (((ULONG)(pwszFileName[2])) + 3) * (((ULONG)(pwszFileName[4])) + 5) % EXCEP_FILE_LIMIT;
  123. iHash2 = (((ULONG)(pwszFileName[9])) + 10) * (((ULONG)(pwszFileName[10])) + 11) % EXCEP_FILE_LIMIT;
  124. }
  125. else if(iLength <= 20)
  126. {
  127. // minimum 16 letter
  128. iHash1 = (((ULONG)(pwszFileName[4])) + 5) * (((ULONG)(pwszFileName[6])) + 7) % EXCEP_FILE_LIMIT;
  129. iHash2 = (((ULONG)(pwszFileName[7])) + 8) * (((ULONG)(pwszFileName[15])) + 16) % EXCEP_FILE_LIMIT;
  130. }
  131. else
  132. {
  133. // minimum 21 letter
  134. iHash1 = (((ULONG)(pwszFileName[11])) + 12) * (((ULONG)(pwszFileName[14])) + 15) % EXCEP_FILE_LIMIT;
  135. iHash2 = (((ULONG)(pwszFileName[17])) + 18) * (((ULONG)(pwszFileName[20])) + 21) % EXCEP_FILE_LIMIT;
  136. }
  137. }
  138. ///////////////////////////////////////////////////////////////////////////////
  139. //
  140. // SaveFileExceptHash, saves the names of the exempt files into the hash table
  141. //
  142. // Parameters:
  143. //
  144. // pwszFileName, the filename that is to be saved
  145. //
  146. // Return:
  147. //
  148. // TRUE for saved, FALSE for redundant file or out of space, not saved
  149. //
  150. ///////////////////////////////////////////////////////////////////////////////
  151. BOOL AnswerParser::SaveFileExceptHash(IN WCHAR* pwszFileName)
  152. {
  153. ULONG iFirstHash = 0;
  154. ULONG iSecondHash = 0;
  155. ULONG iIndex = 0;
  156. ULONG iHashValue = 0;
  157. if(pwszFileName)
  158. {
  159. GetHashValues(pwszFileName, iFirstHash, iSecondHash);
  160. do
  161. {
  162. iHashValue = (iFirstHash + iSecondHash * iIndex) % EXCEP_FILE_LIMIT;
  163. if(m_structHashUsed[iHashValue] == 0)
  164. {
  165. wcsncpy(m_structHash[iHashValue], pwszFileName, SHORT_STRING_LENGTH);
  166. m_structHashUsed[iHashValue] = 1;
  167. return(TRUE);
  168. }
  169. else
  170. {
  171. if(wcscmp(m_structHash[iHashValue], pwszFileName) == 0)
  172. {
  173. // same file name, so treat as error for now
  174. return(FALSE);
  175. }
  176. iIndex++;
  177. }
  178. }
  179. while(iIndex < EXCEP_FILE_LIMIT);
  180. }
  181. // ran out of space
  182. return(FALSE);
  183. }
  184. ///////////////////////////////////////////////////////////////////////////////
  185. //
  186. // IsFileExceptHash, is the file an exempt file?
  187. //
  188. // Parameters:
  189. //
  190. // pwszFileName, the filename that is to be determined
  191. //
  192. // Return:
  193. //
  194. // TRUE for yes, FALSE otherwise
  195. //
  196. ///////////////////////////////////////////////////////////////////////////////
  197. BOOL AnswerParser::IsFileExceptHash(IN CONST WCHAR* pwszFileName)
  198. {
  199. ULONG iFirstHash = 0;
  200. ULONG iSecondHash = 0;
  201. ULONG iIndex = 0;
  202. ULONG iHashValue = 0;
  203. static WCHAR strBuffer[STRING_LENGTH];
  204. if(pwszFileName)
  205. {
  206. wcsncpy(strBuffer, pwszFileName, STRING_LENGTH);
  207. _wcslwr(strBuffer);
  208. GetHashValues(strBuffer, iFirstHash, iSecondHash);
  209. do
  210. {
  211. iHashValue = (iFirstHash + iSecondHash * iIndex) % EXCEP_FILE_LIMIT;
  212. if(m_structHashUsed[iHashValue] == 1)
  213. {
  214. if(wcscmp(m_structHash[iHashValue], strBuffer) == 0)
  215. {
  216. // same file name
  217. return(TRUE);
  218. }
  219. else
  220. {
  221. // space taken, goto the next location
  222. iIndex++;
  223. }
  224. }
  225. else
  226. {
  227. // space is not taken, hash slot empty
  228. return(FALSE);
  229. }
  230. }
  231. while(iIndex < EXCEP_FILE_LIMIT);
  232. }
  233. // ran out of space
  234. return(FALSE);
  235. }
  236. ///////////////////////////////////////////////////////////////////////////////
  237. //
  238. // Parse, the function used to parse the answerfile and fill in the language
  239. // structures
  240. //
  241. // Parameters:
  242. //
  243. // pwszAnswerFile, the answer file's filename
  244. //
  245. // Return:
  246. //
  247. // TRUE for successful parsing, FALSE otherwise
  248. //
  249. ///////////////////////////////////////////////////////////////////////////////
  250. BOOL AnswerParser::Parse(IN CONST WCHAR* pwszAnswerFile)
  251. {
  252. WCHAR strLine[STRING_LENGTH];
  253. WCHAR* strString = NULL;
  254. WCHAR* strStringBefore = NULL;
  255. WCHAR* strStringAfter = NULL;
  256. PPATCH_LANGUAGE thisNode = NULL;
  257. LONG iState = 0;
  258. LONG iBaseCount = 0;
  259. BOOL blnComplete = FALSE;
  260. m_hAnsFile = CreateFileW(pwszAnswerFile,
  261. GENERIC_READ,
  262. 0,
  263. NULL,
  264. OPEN_EXISTING,
  265. FILE_ATTRIBUTE_NORMAL,
  266. NULL);
  267. if(m_hAnsFile != INVALID_HANDLE_VALUE)
  268. {
  269. if(IsUnicodeFile(m_hAnsFile))
  270. {
  271. // state 1 is to exit
  272. // state 0 is to start
  273. // state 2 is in except files
  274. // state 3 is in creating a new language struct
  275. while(iState != 1)
  276. {
  277. switch(iState)
  278. {
  279. case 0:
  280. iState = (ReadLine(m_hAnsFile, strLine) ? 0 : 1);
  281. if(iState == 0 && strLine[0] != L';')
  282. {
  283. strString = wcstok(strLine, L";");
  284. if(strString)
  285. {
  286. if(wcsstr(strString, L"[except]") != NULL)
  287. {
  288. // get except files
  289. iState = 2;
  290. }
  291. else if((strStringBefore = wcschr(strString, L'[')) != NULL &&
  292. (strStringAfter = wcschr(strString, L']')) != NULL)
  293. {
  294. // it's a language
  295. iState = 3;
  296. thisNode = new PATCH_LANGUAGE;
  297. if(thisNode != NULL)
  298. {
  299. CreateNewLanguage(thisNode, strStringBefore, strStringAfter);
  300. }
  301. else
  302. {
  303. iState = 1;
  304. }
  305. }
  306. }
  307. }
  308. break;
  309. case 1:
  310. break;
  311. case 2:
  312. iState = (ReadLine(m_hAnsFile, strLine) ? 2 : 1);
  313. if(iState == 2 && strLine[0] != L';')
  314. {
  315. strString = wcstok(strLine, L";");
  316. if(strString)
  317. {
  318. if(wcscspn(strString, L"[]") == wcslen(strString))
  319. {
  320. SaveFileExceptHash(strString);
  321. }
  322. else if(wcsstr(strString, L"[except]") != NULL)
  323. {
  324. // exception files
  325. }
  326. else if((strStringBefore = wcschr(strString, L'[')) != NULL &&
  327. (strStringAfter = wcschr(strString, L']')) != NULL)
  328. {
  329. // it's a language
  330. iState = 3;
  331. thisNode = new PATCH_LANGUAGE;
  332. if(thisNode != NULL)
  333. {
  334. CreateNewLanguage(thisNode, strStringBefore, strStringAfter);
  335. }
  336. else
  337. {
  338. iState = 1;
  339. }
  340. }
  341. }
  342. }
  343. break;
  344. case 3:
  345. iState = (ReadLine(m_hAnsFile, strLine) ? 3 : 1);
  346. if(iState == 3 && strLine[0] != L';')
  347. {
  348. strString = wcstok(strLine, L";");
  349. if(strString)
  350. {
  351. if(wcscspn(strString, L"[]") == wcslen(strString))
  352. {
  353. // one of the language fields
  354. strStringBefore = wcstok(strString, L"=");
  355. strStringAfter = wcstok(NULL, L"=");
  356. if(strStringBefore && strStringAfter && thisNode)
  357. {
  358. if(_wcsicmp(L"directory", strStringBefore) == 0 &&
  359. (thisNode->s_iDirectoryCount = wcslen(strStringAfter)) > 0)
  360. {
  361. wcscpy(thisNode->s_wszDirectory, strStringAfter);
  362. thisNode->s_iComplete += 1;
  363. }
  364. else if(_wcsicmp(L"base_directory", strStringBefore) == 0 &&
  365. wcslen(strStringAfter) > 0)
  366. {
  367. thisNode->s_blnBase = TRUE;
  368. wcscpy(m_wszBaseDirectory, strStringAfter);
  369. }
  370. else if(_wcsicmp(L"patch_directory", strStringBefore) == 0 &&
  371. (thisNode->s_iPatchDirectoryCount = wcslen(strStringAfter)) > 0)
  372. {
  373. wcscpy(thisNode->s_wszPatchDirectory, strStringAfter);
  374. wcscpy(thisNode->s_wszSubPatchDirectory, strStringAfter);
  375. wcscat(thisNode->s_wszSubPatchDirectory, PATCH_SUB_PATCH);
  376. thisNode->s_iSubPatchDirectoryCount = wcslen(thisNode->s_wszSubPatchDirectory);
  377. wcscpy(thisNode->s_wszSubExceptDirectory, strStringAfter);
  378. wcscat(thisNode->s_wszSubExceptDirectory, PATCH_SUB_EXCEPT);
  379. thisNode->s_iSubExceptDirectoryCount = wcslen(thisNode->s_wszSubExceptDirectory);
  380. wcscpy(thisNode->s_wszScriptFile, strStringAfter);
  381. if(thisNode->s_wszScriptFile[thisNode->s_iPatchDirectoryCount - 1] != L'\\')
  382. {
  383. thisNode->s_wszScriptFile[thisNode->s_iPatchDirectoryCount] = L'\\';
  384. thisNode->s_wszScriptFile[thisNode->s_iPatchDirectoryCount + 1] = 0;
  385. }
  386. wcscat(thisNode->s_wszScriptFile, APPLY_PATCH_SCRIPT);
  387. thisNode->s_iComplete += 1;
  388. }
  389. }
  390. }
  391. else if(wcsstr(strString, L"[except]") != NULL)
  392. {
  393. // exception files
  394. iState = 2;
  395. }
  396. else if((strStringBefore = wcschr(strString, L'[')) != NULL &&
  397. (strStringAfter = wcschr(strString, L']')) != NULL)
  398. {
  399. // it's a language
  400. thisNode = new PATCH_LANGUAGE;
  401. if(thisNode != NULL)
  402. {
  403. CreateNewLanguage(thisNode, strStringBefore, strStringAfter);
  404. }
  405. else
  406. {
  407. iState = 1;
  408. }
  409. }
  410. }
  411. }
  412. break;
  413. default:
  414. iState = 1;
  415. break;
  416. }
  417. }
  418. // end of file now check for validity
  419. if(m_pHead == NULL ||
  420. wcslen(m_wszBaseDirectory) < 1)
  421. {
  422. printf("The file OEMPatch.ans has no base language content. Use /? for help.\n");
  423. return(FALSE);
  424. }
  425. thisNode = m_pHead;
  426. while(thisNode)
  427. {
  428. blnComplete |= (thisNode->s_iComplete == LANGUAGE_COMPLETE);
  429. if(thisNode->s_blnBase) iBaseCount += 1;
  430. thisNode = thisNode->s_pNext;
  431. }
  432. if(!blnComplete)
  433. {
  434. printf("The file OEMPatch.ans has no language specified. Use /? for help.\n");
  435. return(FALSE);
  436. }
  437. if(iBaseCount > 1)
  438. {
  439. printf("The file OEMPatch.ans has more than one base language. Use /? for help.\n");
  440. return(FALSE);
  441. }
  442. }
  443. else
  444. {
  445. printf("The file OEMPatch.ans is not UNICODE as required. Use /? for help.\n");
  446. return(FALSE);
  447. }
  448. CloseHandle(m_hAnsFile);
  449. m_hAnsFile = INVALID_HANDLE_VALUE;
  450. }
  451. else
  452. {
  453. printf("The file OEMPatch.ans cannot be found. Use /? for help.\n");
  454. return(FALSE);
  455. }
  456. m_iBaseDirectoryCount = wcslen(m_wszBaseDirectory);
  457. return(TRUE);
  458. }
  459. BOOL AnswerParser::IsUnicodeFile(IN HANDLE hFile)
  460. {
  461. WCHAR cFirstChar = 0;
  462. ULONG iRead = 0;
  463. if(hFile != INVALID_HANDLE_VALUE &&
  464. ReadFile(hFile, &cFirstChar, sizeof(WCHAR), &iRead, NULL) &&
  465. iRead != 0 &&
  466. cFirstChar == UNICODE_HEAD)
  467. {
  468. return(TRUE);
  469. }
  470. return(FALSE);
  471. }
  472. ///////////////////////////////////////////////////////////////////////////////
  473. //
  474. // ReadLine, this function reads a line from a unicoded file and return the
  475. // contents in strLine, this function should only be called after
  476. // the first two unicoded chars are already read
  477. //
  478. // Parameters:
  479. //
  480. // hFile, the file handle points to the file to read from
  481. // strLine, the buffer that contains the line just read
  482. //
  483. // Return:
  484. //
  485. // TRUE for a line read, FALSE for invalid file, end of file and error in
  486. // read
  487. //
  488. ///////////////////////////////////////////////////////////////////////////////
  489. BOOL AnswerParser::ReadLine(IN HANDLE hFile, IN WCHAR* strLine)
  490. {
  491. // read raw bytes into this buffer, notice the buffer length is 1 over the
  492. // number of total read bytes, it is there to ensure that the last char is
  493. // 0, so that when iLength = 10, which is the same as endofline, the
  494. // wcstok function will return something bizzar
  495. static WCHAR strBuffer[STRING_LENGTH + 1];
  496. static LONG iLength = 0;
  497. static LONG iReadChar = 0;
  498. static ULONG iRead = 0;
  499. static LONG iOffset = 0;
  500. static LONG iThisLineLength = 0;
  501. static WCHAR* strThisLine = NULL;
  502. if(hFile != INVALID_HANDLE_VALUE && strLine)
  503. {
  504. if(iLength > 0)
  505. {
  506. // char 0xA is set to 0
  507. strThisLine = wcstok(strBuffer + iReadChar - iLength, CRETURN);
  508. iThisLineLength = wcslen(strThisLine);
  509. if(iThisLineLength + 1 <= iLength)
  510. {
  511. iLength = iLength - iThisLineLength - 1;
  512. // char 0xD is set to 0
  513. strThisLine[iThisLineLength - 1] = 0;
  514. wcscpy(strLine, strThisLine);
  515. }
  516. else
  517. {
  518. wcsncpy(strLine, strThisLine, iLength);
  519. // set the last char + 1 to 0 for cat
  520. strLine[iLength] = 0;
  521. if(strLine[iLength - 1] != ENDOFLINE[0])
  522. {
  523. ReadFile(hFile, strBuffer, STRING_LENGTH * sizeof(WCHAR), &iRead, NULL);
  524. iReadChar = iRead / sizeof(WCHAR);
  525. // char 0xA is set to 0
  526. strThisLine = wcstok(strBuffer, CRETURN);
  527. iThisLineLength = wcslen(strThisLine);
  528. iLength = iReadChar - iThisLineLength - 1;
  529. // char 0xD is set to 0
  530. strThisLine[iThisLineLength - 1] = 0;
  531. wcscat(strLine, strThisLine);
  532. if(strBuffer[0] == CRETURN[0])
  533. {
  534. iLength -= 1;
  535. }
  536. }
  537. else
  538. {
  539. strLine[iLength - 1] = 0;
  540. iLength = 0;
  541. }
  542. }
  543. }
  544. else
  545. {
  546. if(ReadFile(hFile, strBuffer, STRING_LENGTH * sizeof(WCHAR), &iRead, NULL) && iRead != 0)
  547. {
  548. iReadChar = iRead / sizeof(WCHAR);
  549. // char 0xA is set to 0
  550. strThisLine = wcstok(strBuffer, CRETURN);
  551. iThisLineLength = wcslen(strThisLine);
  552. // iLength is the number of wchars left in strBuffer, subtract it to exclude 0xA
  553. iLength = iReadChar - iThisLineLength - 1;
  554. // char 0xD is set to 0
  555. strThisLine[iThisLineLength - 1] = 0;
  556. wcscpy(strLine, strThisLine);
  557. if(strBuffer[0] == CRETURN[0])
  558. {
  559. iLength -= 1;
  560. }
  561. }
  562. else
  563. {
  564. return(FALSE);
  565. }
  566. }
  567. }
  568. else
  569. {
  570. return(FALSE);
  571. }
  572. return(TRUE);
  573. }
  574. ///////////////////////////////////////////////////////////////////////////////
  575. //
  576. // GetBaseLanguge, get the base language struct to create a base tree, this
  577. // function should only be called after a successful parsing
  578. // of the answer file so that the base language is guanranteed
  579. // to be there
  580. //
  581. // Parameters:
  582. //
  583. // none
  584. //
  585. // Return:
  586. //
  587. // a pointer to the base language structure
  588. //
  589. ///////////////////////////////////////////////////////////////////////////////
  590. PPATCH_LANGUAGE AnswerParser::GetBaseLanguage(VOID)
  591. {
  592. PPATCH_LANGUAGE pPointer = m_pHead;
  593. while(pPointer)
  594. {
  595. if(pPointer->s_blnBase) break;
  596. else pPointer = pPointer->s_pNext;
  597. }
  598. return(pPointer);
  599. }
  600. ///////////////////////////////////////////////////////////////////////////////
  601. //
  602. // GetNextLanguage, get the next language structure that is ready to be
  603. // matched and patched
  604. //
  605. // Parameters:
  606. //
  607. // none
  608. //
  609. // Return:
  610. //
  611. // a pointer to the next language structure
  612. //
  613. ///////////////////////////////////////////////////////////////////////////////
  614. PPATCH_LANGUAGE AnswerParser::GetNextLanguage(VOID)
  615. {
  616. static PPATCH_LANGUAGE pPointer = m_pHead;
  617. PPATCH_LANGUAGE pReturn = NULL;
  618. while(pPointer)
  619. {
  620. if(!pPointer->s_blnBase && pPointer->s_iComplete == LANGUAGE_COMPLETE)
  621. {
  622. pReturn = pPointer;
  623. pPointer = pPointer->s_pNext;
  624. break;
  625. }
  626. pPointer = pPointer->s_pNext;
  627. }
  628. return(pReturn);
  629. }
  630. ///////////////////////////////////////////////////////////////////////////////
  631. //
  632. // CreateNewLanguage, create a new language struct to store information about
  633. // a new language, and link it to the language struct list
  634. //
  635. // Parameters:
  636. //
  637. // pNode, this language struct, empty
  638. // strBegin, the string "[something]"
  639. // strEnd, the string starting at "]"
  640. //
  641. // Return:
  642. //
  643. // none
  644. //
  645. ///////////////////////////////////////////////////////////////////////////////
  646. VOID AnswerParser::CreateNewLanguage(IN PPATCH_LANGUAGE pNode,
  647. IN WCHAR* strBegin,
  648. IN WCHAR* strEnd)
  649. {
  650. wcsncpy(pNode->s_wszLanguage, strBegin + 1,
  651. strEnd - strBegin - 1);
  652. pNode->s_wszLanguage[strEnd - strBegin - 1] = 0;
  653. pNode->s_iComplete += 1;
  654. pNode->s_pNext = m_pHead;
  655. m_pHead = pNode;
  656. }