1 //-----------------------------------------------------------------------------
2 // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // See LICENSE.txt for the text of the license.
15 //-----------------------------------------------------------------------------
17 //-----------------------------------------------------------------------------
19 /* Ensure strtok_r is available even with -std=c99; must be included before
22 #define _POSIX_C_SOURCE 200112L
25 #include "commonutil.h" // ARRAYLEN
26 #include <stdio.h> // for Mingw readline
30 #if defined(HAVE_READLINE)
31 //Load readline after stdio.h
32 #include <readline/readline.h>
37 #include "proxmark3.h" // PROXLOG
38 #include "fileutils.h"
42 # include <direct.h> // _mkdir
47 #include "emojis_alt.h"
48 session_arg_t g_session
;
50 double g_CursorScaleFactor
= 1;
51 char g_CursorScaleFactorUnit
[11] = {0};
52 double g_PlotGridX
= 0, g_PlotGridY
= 0;
53 double g_DefaultGridX
= 64, g_DefaultGridY
= 64;
54 uint32_t g_GraphStart
= 0; // Starting point/offset for the left side of the graph
55 uint32_t g_GraphStop
= 0;
56 uint32_t g_GraphStart_old
= 0;
57 double g_GraphPixelsPerPoint
= 1.f
; // How many visual pixels are between each sample point (x axis)
58 static bool flushAfterWrite
= false;
59 double g_GridOffset
= 0;
60 bool g_GridLocked
= false;
62 pthread_mutex_t g_print_lock
= PTHREAD_MUTEX_INITIALIZER
;
64 static void fPrintAndLog(FILE *stream
, const char *fmt
, ...);
67 #define MKDIR_CHK _mkdir(path)
69 #define MKDIR_CHK mkdir(path, 0700)
73 // needed by flasher, so let's put it here instead of fileutils.c
74 int searchHomeFilePath(char **foundpath
, const char *subdir
, const char *filename
, bool create_home
) {
75 if (foundpath
== NULL
) {
79 const char *user_path
= get_my_user_directory();
80 if (user_path
== NULL
) {
81 fprintf(stderr
, "Could not retrieve $HOME from the environment\n");
85 size_t pathlen
= strlen(user_path
) + strlen(PM3_USER_DIRECTORY
) + 1;
86 char *path
= calloc(pathlen
, sizeof(char));
91 strcpy(path
, user_path
);
92 strcat(path
, PM3_USER_DIRECTORY
);
97 // Mingw _stat fails if path ends with /, so let's use a stripped path
98 if (str_endswith(path
, PATHSEP
)) {
99 memset(path
+ (strlen(path
) - strlen(PATHSEP
)), 0x00, strlen(PATHSEP
));
100 result
= _stat(path
, &st
);
101 strcat(path
, PATHSEP
);
103 result
= _stat(path
, &st
);
107 result
= stat(path
, &st
);
110 if ((result
!= 0) && create_home
) {
113 fprintf(stderr
, "Could not create user directory %s\n", path
);
119 if (subdir
!= NULL
) {
120 pathlen
+= strlen(subdir
);
121 char *tmp
= realloc(path
, pathlen
* sizeof(char));
127 strcat(path
, subdir
);
130 // Mingw _stat fails if path ends with /, so let's use a stripped path
131 if (str_endswith(path
, PATHSEP
)) {
132 memset(path
+ (strlen(path
) - strlen(PATHSEP
)), 0x00, strlen(PATHSEP
));
133 result
= _stat(path
, &st
);
134 strcat(path
, PATHSEP
);
136 result
= _stat(path
, &st
);
139 result
= stat(path
, &st
);
142 if ((result
!= 0) && create_home
) {
145 fprintf(stderr
, "Could not create user directory %s\n", path
);
152 if (filename
== NULL
) {
157 pathlen
+= strlen(filename
);
158 char *tmp
= realloc(path
, pathlen
* sizeof(char));
165 strcat(path
, filename
);
171 void free_grabber(void) {
172 free(g_grabbed_output
.ptr
);
173 g_grabbed_output
.ptr
= NULL
;
174 g_grabbed_output
.size
= 0;
175 g_grabbed_output
.idx
= 0;
178 static void fill_grabber(const char *string
) {
179 if (g_grabbed_output
.ptr
== NULL
|| g_grabbed_output
.size
- g_grabbed_output
.idx
< MAX_PRINT_BUFFER
) {
180 char *tmp
= realloc(g_grabbed_output
.ptr
, g_grabbed_output
.size
+ MAX_PRINT_BUFFER
);
182 // We leave current g_grabbed_output untouched
183 PrintAndLogEx(ERR
, "Out of memory error in fill_grabber()");
186 g_grabbed_output
.ptr
= tmp
;
187 g_grabbed_output
.size
+= MAX_PRINT_BUFFER
;
189 int len
= snprintf(g_grabbed_output
.ptr
+ g_grabbed_output
.idx
, MAX_PRINT_BUFFER
, "%s", string
);
190 if (len
< 0 || len
> MAX_PRINT_BUFFER
) {
191 // We leave current g_grabbed_output_len untouched
192 PrintAndLogEx(ERR
, "snprintf error in fill_grabber()");
195 g_grabbed_output
.idx
+= len
;
198 void PrintAndLogOptions(const char *str
[][2], size_t size
, size_t space
) {
199 char buff
[2000] = "Options:\n";
200 char format
[2000] = "";
201 size_t counts
[2] = {0, 0};
202 for (size_t i
= 0; i
< size
; i
++)
203 for (size_t j
= 0 ; j
< 2 ; j
++)
204 if (counts
[j
] < strlen(str
[i
][j
])) {
205 counts
[j
] = strlen(str
[i
][j
]);
207 for (size_t i
= 0; i
< size
; i
++) {
208 for (size_t j
= 0; j
< 2; j
++) {
210 snprintf(format
, sizeof(format
), "%%%zus%%%zus", space
, counts
[j
]);
212 snprintf(format
, sizeof(format
), "%%%zus%%-%zus", space
, counts
[j
]);
213 snprintf(buff
+ strlen(buff
), sizeof(buff
) - strlen(buff
), format
, " ", str
[i
][j
]);
216 strncat(buff
, "\n", sizeof(buff
) - strlen(buff
) - 1);
218 PrintAndLogEx(NORMAL
, "%s", buff
);
221 static uint8_t PrintAndLogEx_spinidx
= 0;
223 void PrintAndLogEx(logLevel_t level
, const char *fmt
, ...) {
225 // skip debug messages if client debugging is turned off i.e. 'DATA SETDEBUG -0'
226 if (g_debugMode
== 0 && level
== DEBUG
)
229 // skip HINT messages if client has hints turned off i.e. 'HINT 0'
230 if (g_session
.show_hints
== false && level
== HINT
)
233 char prefix
[40] = {0};
234 char buffer
[MAX_PRINT_BUFFER
] = {0};
235 char buffer2
[MAX_PRINT_BUFFER
+ sizeof(prefix
)] = {0};
237 char *tmp_ptr
= NULL
;
238 FILE *stream
= stdout
;
239 const char *spinner
[] = {_YELLOW_("[\\]"), _YELLOW_("[|]"), _YELLOW_("[/]"), _YELLOW_("[-]")};
240 const char *spinner_emoji
[] = {" :clock1: ", " :clock2: ", " :clock3: ", " :clock4: ", " :clock5: ", " :clock6: ",
241 " :clock7: ", " :clock8: ", " :clock9: ", " :clock10: ", " :clock11: ", " :clock12: "
245 if (g_session
.emoji_mode
== EMO_EMOJI
)
246 strncpy(prefix
, "[" _RED_("!!") "] :rotating_light: ", sizeof(prefix
) - 1);
248 strncpy(prefix
, "[" _RED_("!!") "] ", sizeof(prefix
) - 1);
252 if (g_session
.emoji_mode
== EMO_EMOJI
)
253 strncpy(prefix
, "[" _RED_("-") "] :no_entry: ", sizeof(prefix
) - 1);
255 strncpy(prefix
, "[" _RED_("-") "] ", sizeof(prefix
) - 1);
258 strncpy(prefix
, "[" _BLUE_("#") "] ", sizeof(prefix
) - 1);
261 strncpy(prefix
, "[" _YELLOW_("?") "] ", sizeof(prefix
) - 1);
264 strncpy(prefix
, "[" _GREEN_("+") "] ", sizeof(prefix
) - 1);
267 if (g_session
.emoji_mode
== EMO_EMOJI
)
268 strncpy(prefix
, "[" _CYAN_("!") "] :warning: ", sizeof(prefix
) - 1);
270 strncpy(prefix
, "[" _CYAN_("!") "] ", sizeof(prefix
) - 1);
273 strncpy(prefix
, "[" _YELLOW_("=") "] ", sizeof(prefix
) - 1);
276 if (g_session
.emoji_mode
== EMO_EMOJI
) {
277 strncpy(prefix
, spinner_emoji
[PrintAndLogEx_spinidx
], sizeof(prefix
) - 1);
278 PrintAndLogEx_spinidx
++;
279 if (PrintAndLogEx_spinidx
>= ARRAYLEN(spinner_emoji
))
280 PrintAndLogEx_spinidx
= 0;
282 strncpy(prefix
, spinner
[PrintAndLogEx_spinidx
], sizeof(prefix
) - 1);
283 PrintAndLogEx_spinidx
++;
284 if (PrintAndLogEx_spinidx
>= ARRAYLEN(spinner
))
285 PrintAndLogEx_spinidx
= 0;
289 // no prefixes for normal
295 vsnprintf(buffer
, sizeof(buffer
), fmt
, args
);
298 // no prefixes for normal & inplace
299 if (level
== NORMAL
) {
300 fPrintAndLog(stream
, "%s", buffer
);
304 if (strchr(buffer
, '\n')) {
306 const char delim
[2] = "\n";
308 // line starts with newline
309 if (buffer
[0] == '\n')
310 fPrintAndLog(stream
, "");
312 token
= strtok_r(buffer
, delim
, &tmp_ptr
);
314 while (token
!= NULL
) {
316 size_t size
= strlen(buffer2
);
319 snprintf(buffer2
+ size
, sizeof(buffer2
) - size
, "%s%s\n", prefix
, token
);
321 snprintf(buffer2
+ size
, sizeof(buffer2
) - size
, "\n");
323 token
= strtok_r(NULL
, delim
, &tmp_ptr
);
325 fPrintAndLog(stream
, "%s", buffer2
);
327 snprintf(buffer2
, sizeof(buffer2
), "%s%s", prefix
, buffer
);
328 if (level
== INPLACE
) {
329 // ignore INPLACE if rest of output is grabbed
330 if (!(g_printAndLog
& PRINTANDLOG_GRAB
)) {
331 char buffer3
[sizeof(buffer2
)] = {0};
332 char buffer4
[sizeof(buffer2
)] = {0};
333 memcpy_filter_ansi(buffer3
, buffer2
, sizeof(buffer2
), !g_session
.supports_colors
);
334 memcpy_filter_emoji(buffer4
, buffer3
, sizeof(buffer3
), g_session
.emoji_mode
);
335 fprintf(stream
, "\r%s", buffer4
);
339 fPrintAndLog(stream
, "%s", buffer2
);
344 static void fPrintAndLog(FILE *stream
, const char *fmt
, ...) {
346 static FILE *logfile
= NULL
;
347 static int logging
= 1;
348 char buffer
[MAX_PRINT_BUFFER
] = {0};
349 char buffer2
[MAX_PRINT_BUFFER
] = {0};
350 char buffer3
[MAX_PRINT_BUFFER
] = {0};
352 bool linefeed
= true;
354 if (logging
&& g_session
.incognito
) {
357 if ((g_printAndLog
& PRINTANDLOG_LOG
) && logging
&& !logfile
) {
358 char *my_logfile_path
= NULL
;
361 time_t now
= time(NULL
);
362 timenow
= gmtime(&now
);
363 strftime(filename
, sizeof(filename
), PROXLOG
, timenow
);
364 if (searchHomeFilePath(&my_logfile_path
, LOGS_SUBDIR
, filename
, true) != PM3_SUCCESS
) {
365 printf(_YELLOW_("[-]") " Logging disabled!\n");
366 my_logfile_path
= NULL
;
369 logfile
= fopen(my_logfile_path
, "a");
370 if (logfile
== NULL
) {
371 printf(_YELLOW_("[-]") " Can't open logfile %s, logging disabled!\n", my_logfile_path
);
375 if (g_session
.supports_colors
) {
376 printf("["_YELLOW_("=")"] Session log " _YELLOW_("%s") "\n", my_logfile_path
);
378 printf("[=] Session log %s\n", my_logfile_path
);
382 free(my_logfile_path
);
386 // lock this section to avoid interlacing prints from different threads
387 pthread_mutex_lock(&g_print_lock
);
389 // If there is an incoming message from the hardware (eg: lf hid read) in
390 // the background (while the prompt is displayed and accepting user input),
391 // stash the prompt and bring it back later.
392 #ifdef RL_STATE_READCMD
393 // We are using GNU readline. libedit (OSX) doesn't support this flag.
394 int need_hack
= (rl_readline_state
& RL_STATE_READCMD
) > 0;
399 saved_point
= rl_point
;
400 saved_line
= rl_copy_text(0, rl_end
);
402 rl_replace_line("", 0);
407 va_start(argptr
, fmt
);
408 vsnprintf(buffer
, sizeof(buffer
), fmt
, argptr
);
410 if (strlen(buffer
) > 0 && buffer
[strlen(buffer
) - 1] == NOLF
[0]) {
412 buffer
[strlen(buffer
) - 1] = 0;
414 bool filter_ansi
= !g_session
.supports_colors
;
415 memcpy_filter_ansi(buffer2
, buffer
, sizeof(buffer
), filter_ansi
);
416 if (g_printAndLog
& PRINTANDLOG_PRINT
) {
417 memcpy_filter_emoji(buffer3
, buffer2
, sizeof(buffer2
), g_session
.emoji_mode
);
418 fprintf(stream
, "%s", buffer3
);
420 fprintf(stream
, "\n");
423 #ifdef RL_STATE_READCMD
424 // We are using GNU readline. libedit (OSX) doesn't support this flag.
427 rl_replace_line(saved_line
, 0);
428 rl_point
= saved_point
;
434 if (((g_printAndLog
& PRINTANDLOG_LOG
) && logging
&& logfile
) ||
435 (g_printAndLog
& PRINTANDLOG_GRAB
)) {
436 memcpy_filter_emoji(buffer3
, buffer2
, sizeof(buffer2
), EMO_ALTTEXT
);
438 memcpy_filter_ansi(buffer
, buffer3
, sizeof(buffer3
), true);
441 if ((g_printAndLog
& PRINTANDLOG_LOG
) && logging
&& logfile
) {
443 fprintf(logfile
, "%s", buffer3
);
445 fprintf(logfile
, "%s", buffer
);
448 fprintf(logfile
, "\n");
451 if (g_printAndLog
& PRINTANDLOG_GRAB
) {
453 fill_grabber(buffer3
);
455 fill_grabber(buffer
);
465 pthread_mutex_unlock(&g_print_lock
);
468 void SetFlushAfterWrite(bool value
) {
469 flushAfterWrite
= value
;
472 bool GetFlushAfterWrite(void) {
473 return flushAfterWrite
;
476 void memcpy_filter_rlmarkers(void *dest
, const void *src
, size_t n
) {
477 uint8_t *rdest
= (uint8_t *)dest
;
478 uint8_t *rsrc
= (uint8_t *)src
;
480 for (size_t i
= 0; i
< n
; i
++) {
481 if ((rsrc
[i
] == '\001') || (rsrc
[i
] == '\002'))
482 // skip readline special markers
484 rdest
[si
++] = rsrc
[i
];
488 void memcpy_filter_ansi(void *dest
, const void *src
, size_t n
, bool filter
) {
490 // Filter out ANSI sequences on these OS
491 uint8_t *rdest
= (uint8_t *)dest
;
492 uint8_t *rsrc
= (uint8_t *)src
;
494 for (size_t i
= 0; i
< n
; i
++) {
496 && (rsrc
[i
] == '\x1b')
497 && (rsrc
[i
+ 1] >= 0x40)
498 && (rsrc
[i
+ 1] <= 0x5F)) { // entering ANSI sequence
501 if ((i
< n
- 1) && (rsrc
[i
] == '[')) { // entering CSI sequence
504 while ((i
< n
- 1) && (rsrc
[i
] >= 0x30) && (rsrc
[i
] <= 0x3F)) { // parameter bytes
508 while ((i
< n
- 1) && (rsrc
[i
] >= 0x20) && (rsrc
[i
] <= 0x2F)) { // intermediate bytes
512 if ((rsrc
[i
] >= 0x40) && (rsrc
[i
] <= 0x7F)) { // final byte
519 rdest
[si
++] = rsrc
[i
];
522 memcpy(dest
, src
, n
);
526 static bool emojify_token(const char *token
, uint8_t token_length
, const char **emojified_token
, uint8_t *emojified_token_length
, emojiMode_t mode
) {
528 while (EmojiTable
[i
].alias
&& EmojiTable
[i
].emoji
) {
529 if ((strlen(EmojiTable
[i
].alias
) == token_length
) && (0 == memcmp(EmojiTable
[i
].alias
, token
, token_length
))) {
532 *emojified_token
= EmojiTable
[i
].emoji
;
533 *emojified_token_length
= strlen(EmojiTable
[i
].emoji
);
538 *emojified_token_length
= 0;
539 while (EmojiAltTable
[j
].alias
&& EmojiAltTable
[j
].alttext
) {
540 if ((strlen(EmojiAltTable
[j
].alias
) == token_length
) && (0 == memcmp(EmojiAltTable
[j
].alias
, token
, token_length
))) {
541 *emojified_token
= EmojiAltTable
[j
].alttext
;
542 *emojified_token_length
= strlen(EmojiAltTable
[j
].alttext
);
550 *emojified_token_length
= 0;
553 case EMO_ALIAS
: { // should never happen
564 static bool token_charset(uint8_t c
) {
565 if ((c
>= '0') && (c
<= '9')) return true;
566 if ((c
>= 'a') && (c
<= 'z')) return true;
567 if ((c
>= 'A') && (c
<= 'Z')) return true;
568 if ((c
== '_') || (c
== '+') || (c
== '-')) return true;
572 void memcpy_filter_emoji(void *dest
, const void *src
, size_t n
, emojiMode_t mode
) {
573 if (mode
== EMO_ALIAS
) {
574 memcpy(dest
, src
, n
);
577 const char *emojified_token
= NULL
;
578 uint8_t emojified_token_length
= 0;
579 char *current_token
= NULL
;
580 uint8_t current_token_length
= 0;
581 char *rdest
= (char *)dest
;
582 char *rsrc
= (char *)src
;
584 for (size_t i
= 0; i
< n
; i
++) {
585 char current_char
= rsrc
[i
];
587 if (current_token_length
== 0) {
588 // starting a new token.
589 if (current_char
== ':') {
590 current_token
= rsrc
+ i
;
591 current_token_length
= 1;
592 } else { // not starting a new token.
593 rdest
[si
++] = current_char
;
596 // finishing the current token.
597 if (current_char
== ':') {
598 // nothing changed? we still need the ending ':' as it might serve for an upcoming emoji
599 if (! emojify_token(current_token
, current_token_length
+ 1, &emojified_token
, &emojified_token_length
, mode
)) {
600 memcpy(rdest
+ si
, current_token
, current_token_length
);
601 si
+= current_token_length
;
602 current_token
= rsrc
+ i
;
603 current_token_length
= 1;
605 memcpy(rdest
+ si
, emojified_token
, emojified_token_length
);
606 si
+= emojified_token_length
;
607 current_token_length
= 0;
609 } else if (token_charset(current_char
)) { // continuing the current token.
610 current_token_length
++;
611 } else { // dropping the current token.
612 current_token_length
++;
613 memcpy(rdest
+ si
, current_token
, current_token_length
);
614 si
+= current_token_length
;
615 current_token_length
= 0;
619 if (current_token_length
> 0) {
620 memcpy(rdest
+ si
, current_token
, current_token_length
);
626 // If reactivated, beware it doesn't compile on Android (DXL)
627 void iceIIR_Butterworth(int *data, const size_t len) {
629 int *output = (int *) calloc(sizeof(int) * len, sizeof(uint8_t));
633 memset(output, 0x00, len);
635 size_t adjustedLen = len;
636 float fc = 0.1125f; // center frequency
638 // create very simple low-pass filter to remove images (2nd-order Butterworth)
639 float complex iir_buf[3] = {0, 0, 0};
640 float b[3] = {0.003621681514929, 0.007243363029857, 0.003621681514929};
641 float a[3] = {1.000000000000000, -1.822694925196308, 0.837181651256023};
643 for (size_t i = 0; i < adjustedLen; ++i) {
645 float sample = data[i]; // input sample read from array
646 float complex x_prime = 1.0f; // save sample for estimating frequency
649 // remove DC offset and mix to complex baseband
650 x = (sample - 127.5f) * cexpf(_Complex_I * 2 * M_PI * fc * i);
652 // apply low-pass filter, removing spectral image (IIR using direct-form II)
653 iir_buf[2] = iir_buf[1];
654 iir_buf[1] = iir_buf[0];
655 iir_buf[0] = x - a[1] * iir_buf[1] - a[2] * iir_buf[2];
656 x = b[0] * iir_buf[0] +
660 // compute instantaneous frequency by looking at phase difference
661 // between adjacent samples
662 float freq = cargf(x * conjf(x_prime));
663 x_prime = x; // retain this sample for next iteration
665 output[i] = (freq > 0) ? 127 : -127;
669 //memcpy(data, output, adjustedLen);
670 for (size_t j = 0; j < adjustedLen; ++j)
677 void iceSimple_Filter(int *data
, const size_t len
, uint8_t k
) {
678 // ref: http://www.edn.com/design/systems-design/4320010/A-simple-software-lowpass-filter-suits-embedded-system-applications
680 #define FILTER_SHIFT 4
682 int32_t filter_reg
= 0;
683 int8_t shift
= (k
<= 8) ? k
: FILTER_SHIFT
;
685 for (size_t i
= 0; i
< len
; ++i
) {
686 // Update filter with current sample
687 filter_reg
= filter_reg
- (filter_reg
>> shift
) + data
[i
];
689 // Scale output for unity gain
690 data
[i
] = filter_reg
>> shift
;
694 void print_progress(uint64_t count
, uint64_t max
, barMode_t style
) {
696 max
= (count
> max
) ? count
: max
;
697 #if defined(HAVE_READLINE)
698 static int prev_cols
= 0;
700 rl_reset_screen_size(); // refresh Readline idea of the actual screen width
701 rl_get_screen_size(&rows
, &cols
);
707 if (prev_cols
> cols
) {
708 PrintAndLogEx(NORMAL
, _CLEAR_ _TOP_
"");
712 int width
= cols
- 35;
714 #define PERCENTAGE(V, T) ((V * width) / T)
715 // x/8 fractional part of the percentage
716 #define PERCENTAGEFRAC(V, T) ((uint8_t)(((((float)V * width) / T) - ((V * width) / T)) * 8))
718 const char *smoothtable
[] = {
730 int mode
= (g_session
.emoji_mode
== EMO_EMOJI
);
732 const char *block
[] = {"#", "\xe2\x96\x88"};
733 // use a 3-byte space in emoji mode to ease computations
734 const char *space
[] = {" ", "\xe2\x80\x80"};
736 size_t unit
= strlen(block
[mode
]);
738 char *bar
= (char *)calloc(unit
* width
+ 1, sizeof(uint8_t));
740 uint8_t value
= PERCENTAGE(count
, max
);
743 // prefix is added already.
744 for (; i
< unit
* value
; i
+= unit
) {
745 memcpy(bar
+ i
, block
[mode
], unit
);
747 if (i
< unit
* width
) {
750 memcpy(bar
+ i
, smoothtable
[PERCENTAGEFRAC(count
, max
)], unit
);
752 memcpy(bar
+ i
, space
[mode
], unit
);
757 for (; i
< unit
* width
; i
+= unit
) {
758 memcpy(bar
+ i
, space
[mode
], unit
);
761 size_t collen
= strlen(bar
) + 40;
762 char *cbar
= (char *)calloc(collen
, sizeof(uint8_t));
765 if (g_session
.supports_colors
) {
766 int p60
= unit
* (width
* 60 / 100);
767 int p20
= unit
* (width
* 20 / 100);
768 snprintf(cbar
, collen
, _GREEN_("%.*s"), p60
, bar
);
769 snprintf(cbar
+ strlen(cbar
), collen
- strlen(cbar
), _CYAN_("%.*s"), p20
, bar
+ p60
);
770 snprintf(cbar
+ strlen(cbar
), collen
- strlen(cbar
), _YELLOW_("%.*s"), (int)(unit
* width
- p60
- p20
), bar
+ p60
+ p20
);
772 snprintf(cbar
, collen
, "%s", bar
);
777 printf("\b%c[2K\r[" _YELLOW_("=")"] %s", 27, cbar
);
781 printf("\b%c[2K\r[" _YELLOW_("=")"] %s [ %"PRIu64
" mV / %2u V / %2u Vmax ]", 27, cbar
, count
, (uint32_t)(count
/ 1000), (uint32_t)(max
/ 1000));
785 printf("[" _YELLOW_("=")"] %"PRIu64
" mV / %2u V / %2u Vmax \r", count
, (uint32_t)(count
/ 1000), (uint32_t)(max
/ 1000));