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.

595 lines
19 KiB

  1. #include "ulib.hxx"
  2. #include "frsedit.hxx"
  3. #include "untfs.hxx"
  4. #include "frsstruc.hxx"
  5. #include "attrrec.hxx"
  6. #include "cmem.hxx"
  7. #include "ntfssa.hxx"
  8. #include "attrlist.hxx"
  9. #include "crack.hxx"
  10. extern "C" {
  11. #include <stdio.h>
  12. }
  13. BOOLEAN
  14. FRS_EDIT::Initialize(
  15. IN HWND WindowHandle,
  16. IN INT ClientHeight,
  17. IN INT ClientWidth,
  18. IN PLOG_IO_DP_DRIVE Drive
  19. )
  20. {
  21. TEXTMETRIC textmetric;
  22. HDC hdc;
  23. NTFS_SA ntfssa;
  24. MESSAGE msg;
  25. hdc = GetDC(WindowHandle);
  26. if (hdc == NULL)
  27. return FALSE;
  28. GetTextMetrics(hdc, &textmetric);
  29. ReleaseDC(WindowHandle, hdc);
  30. _buffer = NULL;
  31. _size = 0;
  32. _drive = Drive;
  33. if (!_drive) {
  34. return FALSE;
  35. }
  36. if (!ntfssa.Initialize(Drive, &msg) ||
  37. !ntfssa.Read()) {
  38. return FALSE;
  39. }
  40. _cluster_factor = ntfssa.QueryClusterFactor();
  41. _frs_size = ntfssa.QueryFrsSize();
  42. return VERTICAL_TEXT_SCROLL::Initialize(
  43. WindowHandle,
  44. 0,
  45. ClientHeight,
  46. ClientWidth,
  47. textmetric.tmExternalLeading + textmetric.tmHeight,
  48. textmetric.tmMaxCharWidth);
  49. }
  50. VOID
  51. FRS_EDIT::SetBuf(
  52. IN HWND WindowHandle,
  53. IN OUT PVOID Buffer,
  54. IN ULONG Size
  55. )
  56. {
  57. _buffer = Buffer;
  58. _size = Size;
  59. SetScrollPos(WindowHandle, SB_VERT, 0, FALSE);
  60. }
  61. STATIC TCHAR buf[1024];
  62. VOID
  63. FRS_EDIT::Paint(
  64. IN HDC DeviceContext,
  65. IN RECT InvalidRect,
  66. IN HWND WindowHandle
  67. )
  68. {
  69. INT nDrawX, nDrawY;
  70. INT CurrentLine;
  71. PCFILE_RECORD_SEGMENT_HEADER pfrs;
  72. TCHAR sbFlags[32];
  73. SelectObject(DeviceContext, GetStockObject(ANSI_FIXED_FONT));
  74. if (!_buffer || !_size) {
  75. return;
  76. }
  77. pfrs = (PCFILE_RECORD_SEGMENT_HEADER) _buffer;
  78. CurrentLine = 0;
  79. swprintf(buf, TEXT("FILE_RECORD_SEGMENT_HEADER {"));
  80. WriteLine(DeviceContext, CurrentLine++, buf);
  81. swprintf(buf, TEXT(" MULTI_SECTOR_HEADER MultiSectorHeader {"));
  82. WriteLine(DeviceContext, CurrentLine++, buf);
  83. swprintf(buf, TEXT(" Signature: \t\t\t%c%c%c%c"),
  84. pfrs->MultiSectorHeader.Signature[0],
  85. pfrs->MultiSectorHeader.Signature[1],
  86. pfrs->MultiSectorHeader.Signature[2],
  87. pfrs->MultiSectorHeader.Signature[3]);
  88. WriteLine(DeviceContext, CurrentLine++, buf);
  89. swprintf(buf, TEXT(" Update sequence array offset: \t%x"),
  90. pfrs->MultiSectorHeader.UpdateSequenceArrayOffset);
  91. WriteLine(DeviceContext, CurrentLine++, buf);
  92. swprintf(buf, TEXT(" Update sequence array size: \t%x"),
  93. pfrs->MultiSectorHeader.UpdateSequenceArraySize);
  94. WriteLine(DeviceContext, CurrentLine++, buf);
  95. swprintf(buf, TEXT(" }"));
  96. WriteLine(DeviceContext, CurrentLine++, buf);
  97. swprintf(buf, TEXT(" Lsn: \t\t\t<%x,%x>"), pfrs->Lsn.HighPart,
  98. pfrs->Lsn.LowPart);
  99. WriteLine(DeviceContext, CurrentLine++, buf);
  100. swprintf(buf, TEXT(" Sequence number: \t\t%x"), pfrs->SequenceNumber);
  101. WriteLine(DeviceContext, CurrentLine++, buf);
  102. swprintf(buf, TEXT(" Reference count: \t\t%x"),
  103. pfrs->ReferenceCount);
  104. WriteLine(DeviceContext, CurrentLine++, buf);
  105. swprintf(buf, TEXT(" First attribute offset: \t%x"),
  106. pfrs->FirstAttributeOffset);
  107. WriteLine(DeviceContext, CurrentLine++, buf);
  108. if (pfrs->Flags & FILE_RECORD_SEGMENT_IN_USE) {
  109. wcscpy(sbFlags, TEXT("U"));
  110. } else {
  111. wcscpy(sbFlags, TEXT(" "));
  112. }
  113. if (pfrs->Flags & FILE_FILE_NAME_INDEX_PRESENT) {
  114. wcscat(sbFlags, TEXT("I"));
  115. }
  116. swprintf(buf, TEXT(" Flags: \t\t\t%x \t%s"), pfrs->Flags, sbFlags);
  117. WriteLine(DeviceContext, CurrentLine++, buf);
  118. swprintf(buf, TEXT(" First free byte: \t\t%x"), pfrs->FirstFreeByte);
  119. WriteLine(DeviceContext, CurrentLine++, buf);
  120. swprintf(buf, TEXT(" Bytes available: \t\t%x"), pfrs->BytesAvailable);
  121. WriteLine(DeviceContext, CurrentLine++, buf);
  122. swprintf(buf, TEXT(" FILE_REFERENCE BaseFileRecordSegment {"));
  123. WriteLine(DeviceContext, CurrentLine++, buf);
  124. swprintf(buf, TEXT(" LowPart: \t %x"),
  125. pfrs->BaseFileRecordSegment.LowPart);
  126. WriteLine(DeviceContext, CurrentLine++, buf);
  127. swprintf(buf, TEXT(" Sequence number: %x"),
  128. pfrs->BaseFileRecordSegment.SequenceNumber);
  129. WriteLine(DeviceContext, CurrentLine++, buf);
  130. swprintf(buf, TEXT(" }"));
  131. WriteLine(DeviceContext, CurrentLine++, buf);
  132. swprintf(buf, TEXT(" Next attribute instance: %x"),
  133. pfrs->NextAttributeInstance);
  134. WriteLine(DeviceContext, CurrentLine++, buf);
  135. if (pfrs->MultiSectorHeader.UpdateSequenceArrayOffset >=
  136. FIELD_OFFSET(FILE_RECORD_SEGMENT_HEADER, UpdateArrayForCreateOnly)) {
  137. swprintf(buf, TEXT(" Segment Number: <%x,%x>"),
  138. pfrs->SegmentNumberHighPart, pfrs->SegmentNumberLowPart);
  139. WriteLine(DeviceContext, CurrentLine++, buf);
  140. }
  141. swprintf(buf, TEXT("}"));
  142. WriteLine(DeviceContext, CurrentLine++, buf);
  143. CurrentLine++;
  144. // At this point enumerate all of the attribute records.
  145. NTFS_FRS_STRUCTURE frs;
  146. PATTRIBUTE_RECORD_HEADER prec;
  147. NTFS_ATTRIBUTE_RECORD attrrec;
  148. DSTRING record_name;
  149. PWSTR pstr;
  150. CONT_MEM cmem;
  151. if (!cmem.Initialize(_buffer, _size) ||
  152. !frs.Initialize(&cmem, _drive, 0, _cluster_factor,
  153. 0, _frs_size, NULL)) {
  154. return;
  155. }
  156. prec = NULL;
  157. while (prec = (PATTRIBUTE_RECORD_HEADER) frs.GetNextAttributeRecord(prec)) {
  158. if (!attrrec.Initialize(NULL, prec) ||
  159. !attrrec.QueryName(&record_name) ||
  160. !(pstr = record_name.QueryWSTR())) {
  161. return;
  162. }
  163. swprintf(buf, TEXT("ATTRIBUTE_RECORD_HEADER at offset %x {"), (PCHAR) prec - (PCHAR) pfrs);
  164. WriteLine(DeviceContext, CurrentLine++, buf);
  165. PTCHAR SymbolicTypeCode = GetNtfsAttributeTypeCodeName(prec->TypeCode);
  166. swprintf(buf, TEXT(" Type code, name: \t%x (%s), %s"), prec->TypeCode,
  167. NULL == SymbolicTypeCode ? TEXT("unknown") : SymbolicTypeCode,
  168. pstr);
  169. WriteLine(DeviceContext, CurrentLine++, buf);
  170. swprintf(buf, TEXT(" Record length: \t%x"), prec->RecordLength);
  171. WriteLine(DeviceContext, CurrentLine++, buf);
  172. swprintf(buf, TEXT(" Form code: \t\t%x"), prec->FormCode);
  173. WriteLine(DeviceContext, CurrentLine++, buf);
  174. swprintf(buf, TEXT(" Name length: \t%x"), prec->NameLength);
  175. WriteLine(DeviceContext, CurrentLine++, buf);
  176. swprintf(buf, TEXT(" Name offset: \t%x"), prec->NameOffset);
  177. WriteLine(DeviceContext, CurrentLine++, buf);
  178. if (prec->Flags & ATTRIBUTE_FLAG_COMPRESSION_MASK) {
  179. wcscpy(sbFlags, TEXT("C"));
  180. } else {
  181. sbFlags[0] = '\0';
  182. }
  183. swprintf(buf, TEXT(" Flags: \t\t%x \t%s"), prec->Flags, sbFlags);
  184. WriteLine(DeviceContext, CurrentLine++, buf);
  185. swprintf(buf, TEXT(" Instance: \t\t%x"), prec->Instance);
  186. WriteLine(DeviceContext, CurrentLine++, buf);
  187. CurrentLine++;
  188. if (!(prec->FormCode & NONRESIDENT_FORM)) {
  189. //
  190. // Resident Attribute
  191. //
  192. swprintf(buf, TEXT(" Resident form {"));
  193. WriteLine(DeviceContext, CurrentLine++, buf);
  194. swprintf(buf, TEXT(" Value length: \t%x"),
  195. prec->Form.Resident.ValueLength);
  196. WriteLine(DeviceContext, CurrentLine++, buf);
  197. swprintf(buf, TEXT(" Value offset: \t%x"),
  198. prec->Form.Resident.ValueOffset);
  199. WriteLine(DeviceContext, CurrentLine++, buf);
  200. if (prec->Form.Resident.ResidentFlags & RESIDENT_FORM_INDEXED) {
  201. wcscpy(sbFlags, TEXT("I"));
  202. } else {
  203. sbFlags[0] = '\0';
  204. }
  205. swprintf(buf, TEXT(" Resident flags: %x \t%s"),
  206. prec->Form.Resident.ResidentFlags, sbFlags);
  207. WriteLine(DeviceContext, CurrentLine++, buf);
  208. swprintf(buf, TEXT(" }"));
  209. WriteLine(DeviceContext, CurrentLine++, buf);
  210. __try {
  211. if ($FILE_NAME == prec->TypeCode) {
  212. DisplayFileName(prec, DeviceContext, CurrentLine);
  213. } else if ($ATTRIBUTE_LIST == prec->TypeCode) {
  214. DisplayAttrList(prec, DeviceContext, CurrentLine);
  215. } else if ($STANDARD_INFORMATION == prec->TypeCode) {
  216. DisplayStandardInformation( prec, DeviceContext, CurrentLine );
  217. }
  218. } __except (EXCEPTION_EXECUTE_HANDLER) {
  219. swprintf(buf, TEXT("[ADDRESS ERROR]"));
  220. WriteLine(DeviceContext, CurrentLine++, buf);
  221. }
  222. swprintf(buf, TEXT("}"));
  223. WriteLine(DeviceContext, CurrentLine++, buf);
  224. CurrentLine++;
  225. DELETE(pstr);
  226. continue;
  227. }
  228. //
  229. // Nonresident Attribute
  230. //
  231. swprintf(buf, TEXT(" Nonresident form {"));
  232. WriteLine(DeviceContext, CurrentLine++, buf);
  233. swprintf(buf, TEXT(" Lowest vcn: \t\t%x"),
  234. prec->Form.Nonresident.LowestVcn.LowPart);
  235. WriteLine(DeviceContext, CurrentLine++, buf);
  236. swprintf(buf, TEXT(" Highest vcn: \t\t%x"),
  237. prec->Form.Nonresident.HighestVcn.LowPart);
  238. WriteLine(DeviceContext, CurrentLine++, buf);
  239. swprintf(buf, TEXT(" Mapping pairs offset: \t%x"),
  240. prec->Form.Nonresident.MappingPairsOffset);
  241. WriteLine(DeviceContext, CurrentLine++, buf);
  242. swprintf(buf, TEXT(" Compression unit: \t%x"),
  243. prec->Form.Nonresident.CompressionUnit);
  244. WriteLine(DeviceContext, CurrentLine++, buf);
  245. swprintf(buf, TEXT(" Allocated length: \t%x"),
  246. prec->Form.Nonresident.AllocatedLength.LowPart);
  247. WriteLine(DeviceContext, CurrentLine++, buf);
  248. swprintf(buf, TEXT(" File size: \t\t%x"),
  249. prec->Form.Nonresident.FileSize.LowPart);
  250. WriteLine(DeviceContext, CurrentLine++, buf);
  251. swprintf(buf, TEXT(" Valid data length: \t%x"),
  252. prec->Form.Nonresident.ValidDataLength.LowPart);
  253. WriteLine(DeviceContext, CurrentLine++, buf);
  254. if ((prec->Flags & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0) {
  255. swprintf(buf, TEXT(" Total allocated: \t%x"),
  256. prec->Form.Nonresident.TotalAllocated.LowPart);
  257. WriteLine(DeviceContext, CurrentLine++, buf);
  258. }
  259. NTFS_EXTENT_LIST extents;
  260. BIG_INT vcn, lcn, run_length;
  261. ULONG i;
  262. if (!attrrec.QueryExtentList(&extents)) {
  263. return;
  264. }
  265. swprintf(buf, TEXT(" Extent list {"));
  266. WriteLine(DeviceContext, CurrentLine++, buf);
  267. for (i = 0; i < extents.QueryNumberOfExtents(); i++) {
  268. nDrawY = CurrentLine * QueryCharHeight();
  269. if (nDrawY < QueryScrollPosition()*QueryCharHeight()) {
  270. nDrawY += QueryCharHeight();
  271. CurrentLine++;
  272. continue;
  273. }
  274. if (nDrawY > QueryScrollPosition()*QueryCharHeight() +
  275. QueryClientHeight()) {
  276. break;
  277. }
  278. if (!extents.QueryExtent(i, &vcn, &lcn, &run_length)) {
  279. break;
  280. }
  281. swprintf(buf, TEXT(" (vcn, lcn, run length): (%x, %x, %x)"),
  282. vcn.GetLowPart(), lcn.GetLowPart(), run_length.GetLowPart());
  283. WriteLine(DeviceContext, CurrentLine++, buf);
  284. }
  285. swprintf(buf, TEXT(" }"));
  286. WriteLine(DeviceContext, CurrentLine++, buf);
  287. swprintf(buf, TEXT(" }"));
  288. WriteLine(DeviceContext, CurrentLine++, buf);
  289. swprintf(buf, TEXT("}"));
  290. WriteLine(DeviceContext, CurrentLine++, buf);
  291. CurrentLine++;
  292. DELETE(pstr);
  293. }
  294. SetRange(WindowHandle, CurrentLine - 1);
  295. }
  296. VOID
  297. FRS_EDIT::KeyUp(
  298. IN HWND WindowHandle
  299. )
  300. {
  301. ScrollUp(WindowHandle);
  302. }
  303. VOID
  304. FRS_EDIT::KeyDown(
  305. IN HWND WindowHandle
  306. )
  307. {
  308. ScrollDown(WindowHandle);
  309. }
  310. VOID
  311. FRS_EDIT::DisplayStandardInformation(
  312. IN PATTRIBUTE_RECORD_HEADER pRec,
  313. IN HDC DeviceContext,
  314. IN OUT INT &CurrentLine
  315. )
  316. {
  317. PSTANDARD_INFORMATION2 Info2;
  318. PSTANDARD_INFORMATION Info;
  319. PTCHAR pc;
  320. TCHAR sbFlags[32];
  321. Info = (PSTANDARD_INFORMATION ) ((PCHAR)pRec + pRec->Form.Resident.ValueOffset);
  322. Info2 = (PSTANDARD_INFORMATION2) ((PCHAR)pRec + pRec->Form.Resident.ValueOffset);
  323. swprintf( buf, TEXT( " CreationTime: %16I64x" ), Info->CreationTime );
  324. WriteLine( DeviceContext, CurrentLine++, buf );
  325. swprintf( buf, TEXT( " LastModificationTime: %16I64x" ), Info->LastModificationTime );
  326. WriteLine( DeviceContext, CurrentLine++, buf );
  327. swprintf( buf, TEXT( " LastChangeTime: %16I64x" ), Info->LastChangeTime );
  328. WriteLine( DeviceContext, CurrentLine++, buf );
  329. swprintf( buf, TEXT( " LastAccessTime: %16I64x" ), Info->LastAccessTime );
  330. WriteLine( DeviceContext, CurrentLine++, buf );
  331. swprintf( buf, TEXT( " FileAttributes: %08lx" ), Info->FileAttributes );
  332. WriteLine( DeviceContext, CurrentLine++, buf );
  333. swprintf( buf, TEXT( " MaximumVersions: %08lx" ), Info->MaximumVersions );
  334. WriteLine( DeviceContext, CurrentLine++, buf );
  335. swprintf( buf, TEXT( " VersionNumber: %08lx" ), Info->VersionNumber );
  336. WriteLine( DeviceContext, CurrentLine++, buf );
  337. if (pRec->Form.Resident.ValueLength == sizeof( STANDARD_INFORMATION2 )) {
  338. swprintf( buf, TEXT( " ClassId: %08lx" ), Info2->ClassId );
  339. WriteLine( DeviceContext, CurrentLine++, buf );
  340. swprintf( buf, TEXT( " OwnerId: %08lx" ), Info2->OwnerId );
  341. WriteLine( DeviceContext, CurrentLine++, buf );
  342. swprintf( buf, TEXT( " SecurityId: %08lx" ), Info2->SecurityId );
  343. WriteLine( DeviceContext, CurrentLine++, buf );
  344. swprintf( buf, TEXT( " QuotaCharged: %16I64x" ), Info2->QuotaCharged );
  345. WriteLine( DeviceContext, CurrentLine++, buf );
  346. swprintf( buf, TEXT( " Usn: %16I64x" ), Info2->Usn );
  347. WriteLine( DeviceContext, CurrentLine++, buf );
  348. }
  349. }
  350. VOID
  351. FRS_EDIT::DisplayFileName(
  352. IN PATTRIBUTE_RECORD_HEADER pRec,
  353. IN HDC DeviceContext,
  354. IN OUT INT &CurrentLine
  355. )
  356. {
  357. PFILE_NAME pfn;
  358. PTCHAR pc;
  359. TCHAR sbFlags[32];
  360. pfn = (PFILE_NAME)((PCHAR)pRec + pRec->Form.Resident.ValueOffset);
  361. swprintf(buf, TEXT(" File name: \t"));
  362. pc = buf + wcslen(buf);
  363. for (int i = 0; i < min(64, pfn->FileNameLength); ++i) {
  364. *pc++ = (CHAR)pfn->FileName[i];
  365. }
  366. *pc++ = '\0';
  367. if (pfn->FileNameLength > 64) {
  368. wcscat(buf, TEXT("..."));
  369. }
  370. WriteLine(DeviceContext, CurrentLine++, buf);
  371. swprintf(buf, TEXT(" FILE_REFERENCE ParentDirectory {"));
  372. WriteLine(DeviceContext, CurrentLine++, buf);
  373. swprintf(buf, TEXT(" LowPart: \t %x"),
  374. pfn->ParentDirectory.LowPart);
  375. WriteLine(DeviceContext, CurrentLine++, buf);
  376. swprintf(buf, TEXT(" Sequence number: %x"),
  377. pfn->ParentDirectory.SequenceNumber);
  378. WriteLine(DeviceContext, CurrentLine++, buf);
  379. swprintf(buf, TEXT(" }"));
  380. WriteLine(DeviceContext, CurrentLine++, buf);
  381. if (pfn->Flags & FILE_NAME_NTFS) {
  382. wcscpy(sbFlags, TEXT("N"));
  383. } else {
  384. wcscpy(sbFlags, TEXT(" "));
  385. }
  386. if (pfn->Flags & FILE_NAME_DOS) {
  387. wcscat(sbFlags, TEXT("D"));
  388. }
  389. swprintf(buf, TEXT(" Flags: \t%x \t%s"), pfn->Flags, sbFlags);
  390. WriteLine(DeviceContext, CurrentLine++, buf);
  391. }
  392. VOID
  393. FRS_EDIT::DisplayAttrList(
  394. IN PATTRIBUTE_RECORD_HEADER pRec,
  395. IN HDC DeviceContext,
  396. IN OUT INT &CurrentLine
  397. )
  398. {
  399. PATTRIBUTE_LIST_ENTRY CurrentEntry;
  400. PTCHAR pc;
  401. CHAR sbFlags[32];
  402. ULONG LengthOfList = pRec->Form.Resident.ValueLength;
  403. ULONG CurrentOffset;
  404. swprintf(buf, TEXT(" Attribute List Data {"));
  405. WriteLine(DeviceContext, CurrentLine++, buf);
  406. CurrentEntry = (PATTRIBUTE_LIST_ENTRY)((PCHAR)pRec +
  407. pRec->Form.Resident.ValueOffset);
  408. CurrentOffset = 0;
  409. while (CurrentOffset < LengthOfList) {
  410. if (0 != CurrentOffset) {
  411. CurrentLine++;
  412. }
  413. PTCHAR SymbolicTypeCode = GetNtfsAttributeTypeCodeName(
  414. CurrentEntry->AttributeTypeCode);
  415. swprintf(buf, TEXT("\tAttribute type code: \t%x (%s)"),
  416. CurrentEntry->AttributeTypeCode, SymbolicTypeCode);
  417. WriteLine(DeviceContext, CurrentLine++, buf);
  418. swprintf(buf, TEXT("\tRecord length \t\t%x"), CurrentEntry->RecordLength);
  419. WriteLine(DeviceContext, CurrentLine++, buf);
  420. swprintf(buf, TEXT("\tAttribute name length \t%x"),
  421. CurrentEntry->AttributeNameLength);
  422. WriteLine(DeviceContext, CurrentLine++, buf);
  423. swprintf(buf, TEXT("\tAttribute name offset \t%x"),
  424. CurrentEntry->AttributeNameOffset);
  425. WriteLine(DeviceContext, CurrentLine++, buf);
  426. swprintf(buf, TEXT("\tLowest vcn \t\t<%x,%x>"),
  427. CurrentEntry->LowestVcn.GetHighPart(),
  428. CurrentEntry->LowestVcn.GetLowPart());
  429. WriteLine(DeviceContext, CurrentLine++, buf);
  430. swprintf(buf, TEXT("\tSegment reference: \t<%x,%x>"),
  431. CurrentEntry->SegmentReference.HighPart,
  432. CurrentEntry->SegmentReference.LowPart,
  433. CurrentEntry->SegmentReference.SequenceNumber);
  434. WriteLine(DeviceContext, CurrentLine++, buf);
  435. swprintf(buf, TEXT("\tSequence number: \t%x"),
  436. CurrentEntry->SegmentReference.SequenceNumber);
  437. WriteLine(DeviceContext, CurrentLine++, buf);
  438. swprintf(buf, TEXT("\tInstance: \t\t%x"), CurrentEntry->Instance);
  439. WriteLine(DeviceContext, CurrentLine++, buf);
  440. swprintf(buf, TEXT("\tAttribute name:\t\t"));
  441. pc = buf + wcslen(buf);
  442. for (int i = 0; i < min(64, CurrentEntry->AttributeNameLength); ++i) {
  443. *pc++ = (CHAR)CurrentEntry->AttributeName[i];
  444. }
  445. *pc++ = '\0';
  446. if (CurrentEntry->AttributeNameLength > 64) {
  447. wcscat(buf, TEXT("..."));
  448. }
  449. WriteLine(DeviceContext, CurrentLine++, buf);
  450. CurrentOffset += CurrentEntry->RecordLength;
  451. CurrentEntry = NextEntry(CurrentEntry);
  452. }
  453. swprintf(buf, TEXT(" }"));
  454. WriteLine(DeviceContext, CurrentLine++, buf);
  455. }