aboutsummaryrefslogtreecommitdiff
path: root/src/dutil/conutil.cpp
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-09-03 11:22:38 -0700
committerRob Mensching <rob@firegiant.com>2017-09-03 13:33:33 -0700
commit5d8375007754101ff2889d0e79486c8f9b7cf5ab (patch)
treea76d6fb6a38dd9f04a93ffcfd9d64e76779b3414 /src/dutil/conutil.cpp
parent8e8da6dbc051ec884b5d439bb4f44dc027d05bbf (diff)
downloadwix-5d8375007754101ff2889d0e79486c8f9b7cf5ab.tar.gz
wix-5d8375007754101ff2889d0e79486c8f9b7cf5ab.tar.bz2
wix-5d8375007754101ff2889d0e79486c8f9b7cf5ab.zip
Initial commit
Diffstat (limited to 'src/dutil/conutil.cpp')
-rw-r--r--src/dutil/conutil.cpp656
1 files changed, 656 insertions, 0 deletions
diff --git a/src/dutil/conutil.cpp b/src/dutil/conutil.cpp
new file mode 100644
index 00000000..4c820a1c
--- /dev/null
+++ b/src/dutil/conutil.cpp
@@ -0,0 +1,656 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5
6static HANDLE vhStdIn = INVALID_HANDLE_VALUE;
7static HANDLE vhStdOut = INVALID_HANDLE_VALUE;
8static BOOL vfConsoleIn = FALSE;
9static BOOL vfConsoleOut = FALSE;
10static CONSOLE_SCREEN_BUFFER_INFO vcsbiInfo;
11
12
13extern "C" HRESULT DAPI ConsoleInitialize()
14{
15 Assert(INVALID_HANDLE_VALUE == vhStdOut);
16 HRESULT hr = S_OK;
17 UINT er;
18
19 vhStdIn = ::GetStdHandle(STD_INPUT_HANDLE);
20 if (INVALID_HANDLE_VALUE == vhStdIn)
21 {
22 ExitOnLastError(hr, "failed to open stdin");
23 }
24
25 vhStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
26 if (INVALID_HANDLE_VALUE == vhStdOut)
27 {
28 ExitOnLastError(hr, "failed to open stdout");
29 }
30
31 // check if we have a std in on the console
32 if (::GetConsoleScreenBufferInfo(vhStdIn, &vcsbiInfo))
33 {
34 vfConsoleIn = TRUE;
35 }
36 else
37 {
38 er = ::GetLastError();
39 if (ERROR_INVALID_HANDLE == er)
40 {
41 vfConsoleIn= FALSE;
42 hr = S_OK;
43 }
44 else
45 {
46 ExitOnWin32Error(er, hr, "failed to get input console screen buffer info");
47 }
48 }
49
50 if (::GetConsoleScreenBufferInfo(vhStdOut, &vcsbiInfo))
51 {
52 vfConsoleOut = TRUE;
53 }
54 else // no console
55 {
56 memset(&vcsbiInfo, 0, sizeof(vcsbiInfo));
57 er = ::GetLastError();
58 if (ERROR_INVALID_HANDLE == er)
59 {
60 vfConsoleOut = FALSE;
61 hr = S_OK;
62 }
63 else
64 {
65 ExitOnWin32Error(er, hr, "failed to get output console screen buffer info");
66 }
67 }
68
69LExit:
70 if (FAILED(hr))
71 {
72 if (INVALID_HANDLE_VALUE != vhStdOut)
73 {
74 ::CloseHandle(vhStdOut);
75 }
76
77 if (INVALID_HANDLE_VALUE != vhStdIn && vhStdOut != vhStdIn)
78 {
79 ::CloseHandle(vhStdIn);
80 }
81
82 vhStdOut = INVALID_HANDLE_VALUE;
83 vhStdIn = INVALID_HANDLE_VALUE;
84 }
85
86 return hr;
87}
88
89
90extern "C" void DAPI ConsoleUninitialize()
91{
92 memset(&vcsbiInfo, 0, sizeof(vcsbiInfo));
93
94 if (INVALID_HANDLE_VALUE != vhStdOut)
95 {
96 ::CloseHandle(vhStdOut);
97 }
98
99 if (INVALID_HANDLE_VALUE != vhStdIn && vhStdOut != vhStdIn)
100 {
101 ::CloseHandle(vhStdIn);
102 }
103
104 vhStdOut = INVALID_HANDLE_VALUE;
105 vhStdIn = INVALID_HANDLE_VALUE;
106}
107
108
109extern "C" void DAPI ConsoleGreen()
110{
111 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
112 if (vfConsoleOut)
113 {
114 ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
115 }
116}
117
118
119extern "C" void DAPI ConsoleRed()
120{
121 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
122 if (vfConsoleOut)
123 {
124 ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_RED | FOREGROUND_INTENSITY);
125 }
126}
127
128
129extern "C" void DAPI ConsoleYellow()
130{
131 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
132 if (vfConsoleOut)
133 {
134 ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
135 }
136}
137
138
139extern "C" void DAPI ConsoleNormal()
140{
141 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
142 if (vfConsoleOut)
143 {
144 ::SetConsoleTextAttribute(vhStdOut, vcsbiInfo.wAttributes);
145 }
146}
147
148
149/********************************************************************
150 ConsoleWrite - full color printfA without libc
151
152 NOTE: use FormatMessage formatting ("%1" or "%1!d!") not plain printf formatting ("%ls" or "%d")
153 assumes already in normal color and resets the screen to normal color
154********************************************************************/
155extern "C" HRESULT DAPI ConsoleWrite(
156 CONSOLE_COLOR cc,
157 __in_z __format_string LPCSTR szFormat,
158 ...
159 )
160{
161 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
162 HRESULT hr = S_OK;
163 LPSTR pszOutput = NULL;
164 DWORD cchOutput = 0;
165 DWORD cbWrote = 0;
166 DWORD cbTotal = 0;
167
168 // set the color
169 switch (cc)
170 {
171 case CONSOLE_COLOR_NORMAL: break; // do nothing
172 case CONSOLE_COLOR_RED: ConsoleRed(); break;
173 case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break;
174 case CONSOLE_COLOR_GREEN: ConsoleGreen(); break;
175 }
176
177 va_list args;
178 va_start(args, szFormat);
179 hr = StrAnsiAllocFormattedArgs(&pszOutput, szFormat, args);
180 va_end(args);
181 ExitOnFailure(hr, "failed to format message: \"%s\"", szFormat);
182
183 cchOutput = lstrlenA(pszOutput);
184 while (cbTotal < (sizeof(*pszOutput) * cchOutput))
185 {
186 if (!::WriteFile(vhStdOut, reinterpret_cast<BYTE*>(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL))
187 {
188 ExitOnLastError(hr, "failed to write output to console: %s", pszOutput);
189 }
190
191 cbTotal += cbWrote;
192 }
193
194 // reset the color to normal
195 if (CONSOLE_COLOR_NORMAL != cc)
196 {
197 ConsoleNormal();
198 }
199
200LExit:
201 ReleaseStr(pszOutput);
202 return hr;
203}
204
205
206/********************************************************************
207 ConsoleWriteLine - full color printfA plus newline without libc
208
209 NOTE: use FormatMessage formatting ("%1" or "%1!d!") not plain printf formatting ("%ls" or "%d")
210 assumes already in normal color and resets the screen to normal color
211********************************************************************/
212extern "C" HRESULT DAPI ConsoleWriteLine(
213 CONSOLE_COLOR cc,
214 __in_z __format_string LPCSTR szFormat,
215 ...
216 )
217{
218 AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called");
219 HRESULT hr = S_OK;
220 LPSTR pszOutput = NULL;
221 DWORD cchOutput = 0;
222 DWORD cbWrote = 0;
223 DWORD cbTotal = 0;
224 LPCSTR szNewLine = "\r\n";
225
226 // set the color
227 switch (cc)
228 {
229 case CONSOLE_COLOR_NORMAL: break; // do nothing
230 case CONSOLE_COLOR_RED: ConsoleRed(); break;
231 case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break;
232 case CONSOLE_COLOR_GREEN: ConsoleGreen(); break;
233 }
234
235 va_list args;
236 va_start(args, szFormat);
237 hr = StrAnsiAllocFormattedArgs(&pszOutput, szFormat, args);
238 va_end(args);
239 ExitOnFailure(hr, "failed to format message: \"%s\"", szFormat);
240
241 //
242 // write the string
243 //
244 cchOutput = lstrlenA(pszOutput);
245 while (cbTotal < (sizeof(*pszOutput) * cchOutput))
246 {
247 if (!::WriteFile(vhStdOut, reinterpret_cast<BYTE*>(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL))
248 ExitOnLastError(hr, "failed to write output to console: %s", pszOutput);
249
250 cbTotal += cbWrote;
251 }
252
253 //
254 // write the newline
255 //
256 if (!::WriteFile(vhStdOut, reinterpret_cast<const BYTE*>(szNewLine), 2, &cbWrote, NULL))
257 {
258 ExitOnLastError(hr, "failed to write newline to console");
259 }
260
261 // reset the color to normal
262 if (CONSOLE_COLOR_NORMAL != cc)
263 {
264 ConsoleNormal();
265 }
266
267LExit:
268 ReleaseStr(pszOutput);
269 return hr;
270}
271
272
273/********************************************************************
274 ConsoleWriteError - display an error to the screen
275
276 NOTE: use FormatMessage formatting ("%1" or "%1!d!") not plain printf formatting ("%s" or "%d")
277********************************************************************/
278HRESULT ConsoleWriteError(
279 HRESULT hrError,
280 CONSOLE_COLOR cc,
281 __in_z __format_string LPCSTR szFormat,
282 ...
283 )
284{
285 HRESULT hr = S_OK;
286 LPSTR pszMessage = NULL;
287
288 va_list args;
289 va_start(args, szFormat);
290 hr = StrAnsiAllocFormattedArgs(&pszMessage, szFormat, args);
291 va_end(args);
292 ExitOnFailure(hr, "failed to format error message: \"%s\"", szFormat);
293
294 if (FAILED(hrError))
295 {
296 hr = ConsoleWriteLine(cc, "Error 0x%x: %s", hrError, pszMessage);
297 }
298 else
299 {
300 hr = ConsoleWriteLine(cc, "Error: %s", pszMessage);
301 }
302
303LExit:
304 ReleaseStr(pszMessage);
305 return hr;
306}
307
308
309/********************************************************************
310 ConsoleReadW - get console input without libc
311
312 NOTE: only supports reading ANSI characters
313********************************************************************/
314extern "C" HRESULT DAPI ConsoleReadW(
315 __deref_out_z LPWSTR* ppwzBuffer
316 )
317{
318 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
319 Assert(ppwzBuffer);
320
321 HRESULT hr = S_OK;
322 LPSTR psz = NULL;
323 DWORD cch = 0;
324 DWORD cchRead = 0;
325 DWORD cchTotalRead = 0;
326
327 cch = 64;
328 hr = StrAnsiAlloc(&psz, cch);
329 ExitOnFailure(hr, "failed to allocate memory to read from console");
330
331 // loop until we read the \r\n from the console
332 for (;;)
333 {
334 // read one character at a time, since that seems to be the only way to make this work
335 if (!::ReadFile(vhStdIn, psz + cchTotalRead, 1, &cchRead, NULL))
336 ExitOnLastError(hr, "failed to read string from console");
337
338 cchTotalRead += cchRead;
339 if (1 < cchTotalRead && '\r' == psz[cchTotalRead - 2] || '\n' == psz[cchTotalRead - 1])
340 {
341 psz[cchTotalRead - 2] = '\0'; // chop off the \r\n
342 break;
343 }
344 else if (0 == cchRead) // nothing more was read
345 {
346 psz[cchTotalRead] = '\0'; // null termintate and bail
347 break;
348 }
349
350 if (cchTotalRead == cch)
351 {
352 cch *= 2; // double everytime we run out of space
353 hr = StrAnsiAlloc(&psz, cch);
354 ExitOnFailure(hr, "failed to allocate memory to read from console");
355 }
356 }
357
358 hr = StrAllocStringAnsi(ppwzBuffer, psz, 0, CP_ACP);
359
360LExit:
361 ReleaseStr(psz);
362 return hr;
363}
364
365
366/********************************************************************
367 ConsoleReadNonBlockingW - Read from the console without blocking
368 Won't work for redirected files (exe < txtfile), but will work for stdin redirected to
369 an anonymous or named pipe
370
371 if (fReadLine), stop reading immediately when \r\n is found
372*********************************************************************/
373extern "C" HRESULT DAPI ConsoleReadNonBlockingW(
374 __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer,
375 __out DWORD* pcchSize,
376 BOOL fReadLine
377 )
378{
379 Assert(INVALID_HANDLE_VALUE != vhStdIn && pcchSize);
380 HRESULT hr = S_OK;
381
382 LPSTR psz = NULL;
383
384 ExitOnNull(ppwzBuffer, hr, E_INVALIDARG, "Failed to read from console because buffer was not provided");
385
386 DWORD dwRead;
387 DWORD dwNumInput;
388
389 DWORD cchTotal = 0;
390 DWORD cch = 8;
391
392 DWORD cchRead = 0;
393 DWORD cchTotalRead = 0;
394
395 DWORD dwIndex = 0;
396 DWORD er;
397
398 INPUT_RECORD ir;
399 WCHAR chIn;
400
401 *ppwzBuffer = NULL;
402 *pcchSize = 0;
403
404 // If we really have a handle to stdin, and not the end of a pipe
405 if (!PeekNamedPipe(vhStdIn, NULL, 0, NULL, &dwRead, NULL))
406 {
407 er = ::GetLastError();
408 if (ERROR_INVALID_HANDLE != er)
409 {
410 ExitFunction1(hr = HRESULT_FROM_WIN32(er));
411 }
412
413 if (!GetNumberOfConsoleInputEvents(vhStdIn, &dwRead))
414 {
415 ExitOnLastError(hr, "failed to peek at console input");
416 }
417
418 if (0 == dwRead)
419 {
420 ExitFunction1(hr = S_FALSE);
421 }
422
423 for (/* dwRead from num of input events */; dwRead > 0; dwRead--)
424 {
425 if (!ReadConsoleInputW(vhStdIn, &ir, 1, &dwNumInput))
426 {
427 ExitOnLastError(hr, "Failed to read input from console");
428 }
429
430 // If what we have is a KEY_EVENT, and that event signifies keyUp, we're interested
431 if (KEY_EVENT == ir.EventType && FALSE == ir.Event.KeyEvent.bKeyDown)
432 {
433 chIn = ir.Event.KeyEvent.uChar.UnicodeChar;
434
435 if (0 == cchTotal)
436 {
437 cchTotal = cch;
438 cch *= 2;
439 StrAlloc(ppwzBuffer, cch);
440 }
441
442 (*ppwzBuffer)[dwIndex] = chIn;
443
444 if (fReadLine && (L'\r' == (*ppwzBuffer)[dwIndex - 1] && L'\n' == (*ppwzBuffer)[dwIndex]))
445 {
446 *ppwzBuffer[dwIndex - 1] = L'\0';
447 dwIndex -= 1;
448 break;
449 }
450
451 ++dwIndex;
452 cchTotal--;
453 }
454 }
455
456 *pcchSize = dwIndex;
457 }
458 else
459 {
460 // otherwise, the peek worked, and we have the end of a pipe
461 if (0 == dwRead)
462 ExitFunction1(hr = S_FALSE);
463
464 cch = 8;
465 hr = StrAnsiAlloc(&psz, cch);
466 ExitOnFailure(hr, "failed to allocate memory to read from console");
467
468 for (/*dwRead from PeekNamedPipe*/; dwRead > 0; dwRead--)
469 {
470 // read one character at a time, since that seems to be the only way to make this work
471 if (!::ReadFile(vhStdIn, psz + cchTotalRead, 1, &cchRead, NULL))
472 {
473 ExitOnLastError(hr, "failed to read string from console");
474 }
475
476 cchTotalRead += cchRead;
477 if (fReadLine && '\r' == psz[cchTotalRead - 1] && '\n' == psz[cchTotalRead])
478 {
479 psz[cchTotalRead - 1] = '\0'; // chop off the \r\n
480 cchTotalRead -= 1;
481 break;
482 }
483 else if (0 == cchRead) // nothing more was read
484 {
485 psz[cchTotalRead] = '\0'; // null termintate and bail
486 break;
487 }
488
489 if (cchTotalRead == cch)
490 {
491 cch *= 2; // double everytime we run out of space
492 hr = StrAnsiAlloc(&psz, cch);
493 ExitOnFailure(hr, "failed to allocate memory to read from console");
494 }
495 }
496
497 *pcchSize = cchTotalRead;
498 hr = StrAllocStringAnsi(ppwzBuffer, psz, cchTotalRead, CP_ACP);
499 }
500
501LExit:
502 ReleaseStr(psz);
503
504 return hr;
505}
506
507
508/********************************************************************
509 ConsoleReadStringA - get console input without libc
510
511*********************************************************************/
512extern "C" HRESULT DAPI ConsoleReadStringA(
513 __deref_out_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPSTR* ppszCharBuffer,
514 CONST DWORD cchCharBuffer,
515 __out DWORD* pcchNumCharReturn
516 )
517{
518 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
519 HRESULT hr = S_OK;
520 if (ppszCharBuffer && (pcchNumCharReturn || cchCharBuffer < 2))
521 {
522 DWORD iRead = 1;
523 DWORD iReadCharTotal = 0;
524 if (ppszCharBuffer && *ppszCharBuffer == NULL)
525 {
526 do
527 {
528 hr = StrAnsiAlloc(ppszCharBuffer, cchCharBuffer * iRead);
529 ExitOnFailure(hr, "failed to allocate memory for ConsoleReadStringW");
530 // ReadConsoleW will not return until <Return>, the last two chars are 13 and 10.
531 if (!::ReadConsoleA(vhStdIn, *ppszCharBuffer + iReadCharTotal, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn == 0)
532 {
533 ExitOnLastError(hr, "failed to read string from console");
534 }
535 iReadCharTotal += *pcchNumCharReturn;
536 iRead += 1;
537 }
538 while((*ppszCharBuffer)[iReadCharTotal - 1] != 10 || (*ppszCharBuffer)[iReadCharTotal - 2] != 13);
539 *pcchNumCharReturn = iReadCharTotal;
540 }
541 else
542 {
543 if (!::ReadConsoleA(vhStdIn, *ppszCharBuffer, cchCharBuffer, pcchNumCharReturn, NULL) ||
544 *pcchNumCharReturn > cchCharBuffer || *pcchNumCharReturn == 0)
545 {
546 ExitOnLastError(hr, "failed to read string from console");
547 }
548 if ((*ppszCharBuffer)[*pcchNumCharReturn - 1] != 10 ||
549 (*ppszCharBuffer)[*pcchNumCharReturn - 2] != 13)
550 {
551 // need read more
552 hr = ERROR_MORE_DATA;
553 }
554 }
555 }
556 else
557 {
558 hr = E_INVALIDARG;
559 }
560
561LExit:
562 return hr;
563}
564
565/********************************************************************
566 ConsoleReadStringW - get console input without libc
567
568*********************************************************************/
569extern "C" HRESULT DAPI ConsoleReadStringW(
570 __deref_out_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPWSTR* ppwzCharBuffer,
571 const DWORD cchCharBuffer,
572 __out DWORD* pcchNumCharReturn
573 )
574{
575 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
576 HRESULT hr = S_OK;
577 if (ppwzCharBuffer && (pcchNumCharReturn || cchCharBuffer < 2))
578 {
579 DWORD iRead = 1;
580 DWORD iReadCharTotal = 0;
581 if (*ppwzCharBuffer == NULL)
582 {
583 do
584 {
585 hr = StrAlloc(ppwzCharBuffer, cchCharBuffer * iRead);
586 ExitOnFailure(hr, "failed to allocate memory for ConsoleReadStringW");
587 // ReadConsoleW will not return until <Return>, the last two chars are 13 and 10.
588 if (!::ReadConsoleW(vhStdIn, *ppwzCharBuffer + iReadCharTotal, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn == 0)
589 {
590 ExitOnLastError(hr, "failed to read string from console");
591 }
592 iReadCharTotal += *pcchNumCharReturn;
593 iRead += 1;
594 }
595 while((*ppwzCharBuffer)[iReadCharTotal - 1] != 10 || (*ppwzCharBuffer)[iReadCharTotal - 2] != 13);
596 *pcchNumCharReturn = iReadCharTotal;
597 }
598 else
599 {
600 if (!::ReadConsoleW(vhStdIn, *ppwzCharBuffer, cchCharBuffer, pcchNumCharReturn, NULL) ||
601 *pcchNumCharReturn > cchCharBuffer || *pcchNumCharReturn == 0)
602 {
603 ExitOnLastError(hr, "failed to read string from console");
604 }
605 if ((*ppwzCharBuffer)[*pcchNumCharReturn - 1] != 10 ||
606 (*ppwzCharBuffer)[*pcchNumCharReturn - 2] != 13)
607 {
608 // need read more
609 hr = ERROR_MORE_DATA;
610 }
611 }
612 }
613 else
614 {
615 hr = E_INVALIDARG;
616 }
617
618LExit:
619 return hr;
620}
621
622/********************************************************************
623 ConsoleSetReadHidden - set console input no echo
624
625*********************************************************************/
626extern "C" HRESULT DAPI ConsoleSetReadHidden(void)
627{
628 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
629 HRESULT hr = S_OK;
630 ::FlushConsoleInputBuffer(vhStdIn);
631 if (!::SetConsoleMode(vhStdIn, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT))
632 {
633 ExitOnLastError(hr, "failed to set console input mode to be hidden");
634 }
635
636LExit:
637 return hr;
638}
639
640/********************************************************************
641 ConsoleSetReadNormal - reset to echo
642
643*********************************************************************/
644extern "C" HRESULT DAPI ConsoleSetReadNormal(void)
645{
646 AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called");
647 HRESULT hr = S_OK;
648 if (!::SetConsoleMode(vhStdIn, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT))
649 {
650 ExitOnLastError(hr, "failed to set console input mode to be normal");
651 }
652
653LExit:
654 return hr;
655}
656