1 /* ui.cpp --
2
3 This file is part of the UPX executable compressor.
4
5 Copyright (C) 1996-2020 Markus Franz Xaver Johannes Oberhumer
6 Copyright (C) 1996-2020 Laszlo Molnar
7 All Rights Reserved.
8
9 UPX and the UCL library are free software; you can redistribute them
10 and/or modify them under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of
12 the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; see the file COPYING.
21 If not, write to the Free Software Foundation, Inc.,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 Markus F.X.J. Oberhumer Laszlo Molnar
25 <markus@oberhumer.com> <ezerotven+github@gmail.com>
26 */
27
28 #include "conf.h"
29 #include "file.h"
30 #include "ui.h"
31 #include "screen.h"
32 #include "packer.h"
33
34 #if 1 && (USE_SCREEN)
35 #define UI_USE_SCREEN 1
36 #endif
37
38 enum {
39 M_QUIET, // nothing at all '-qqq'
40 M_INFO, // print a one line info after compression '-qq'
41 M_MSG, // print "compressing", then "\r" and M_INFO
42 M_CB_TERM, // 1 line callback using stdout
43 M_CB_SCREEN // 2 line callback using screen
44 };
45
46 struct UiPacker::State {
47 int mode;
48
49 unsigned u_len;
50 unsigned step;
51 unsigned next_update;
52
53 int pass;
54 int total_passes;
55
56 // message stuff
57 char msg_buf[1 + 79 + 1];
58 int pos; // last progress bar position
59 unsigned spin_counter; // for spinner
60
61 int bar_pos;
62 int bar_len;
63 int pass_digits; // number of digits needed to print total_passes
64
65 #if (UI_USE_SCREEN)
66 screen_t *screen;
67 int screen_init_done;
68 int b_cx, b_cy;
69 int s_cx, s_cy;
70 int s_fg, s_bg;
71 int c_fg;
72 int scroll_up;
73 int cursor_shape;
74 #else
75 void *screen;
76 #endif
77 };
78
79 unsigned UiPacker::total_files = 0;
80 unsigned UiPacker::total_files_done = 0;
81 upx_uint64_t UiPacker::total_c_len = 0;
82 upx_uint64_t UiPacker::total_u_len = 0;
83 upx_uint64_t UiPacker::total_fc_len = 0;
84 upx_uint64_t UiPacker::total_fu_len = 0;
85 unsigned UiPacker::update_c_len = 0;
86 unsigned UiPacker::update_u_len = 0;
87 unsigned UiPacker::update_fc_len = 0;
88 unsigned UiPacker::update_fu_len = 0;
89
90 /*************************************************************************
91 // constants
92 **************************************************************************/
93
94 static const char header_line1[] = " File size Ratio Format Name\n";
95 static char header_line2[] = " -------------------- ------ ----------- -----------\n";
96
97 static char progress_filler[] = ".*[]";
98
init_global_constants(void)99 static void init_global_constants(void) {
100 #if 0 && (ACC_OS_DOS16 || ACC_OS_DOS32)
101 // FIXME: should test codepage here
102
103 static bool done = false;
104 if (done)
105 return;
106 done = true;
107
108 #if 1 && (ACC_OS_DOS32) && defined(__DJGPP__)
109 /* check for Windows NT/2000/XP */
110 if (_get_dos_version(1) == 0x0532)
111 return;
112 #endif
113
114 char *p;
115 for (p = header_line2; *p; p++)
116 if (*p == '-')
117 *p = '\xc4';
118
119 //strcpy(progress_filler, "\x07\xb0[]");
120 //strcpy(progress_filler, "\x07\xb1[]");
121 strcpy(progress_filler, "\xf9\xfe[]");
122 #endif
123 }
124
125 /*************************************************************************
126 //
127 **************************************************************************/
128
mkline(upx_uint64_t fu_len,upx_uint64_t fc_len,upx_uint64_t u_len,upx_uint64_t c_len,const char * format_name,const char * filename,bool decompress=false)129 static const char *mkline(upx_uint64_t fu_len, upx_uint64_t fc_len, upx_uint64_t u_len,
130 upx_uint64_t c_len, const char *format_name, const char *filename,
131 bool decompress = false) {
132 static char buf[2048];
133 char r[7 + 1];
134 char fn[15 + 1];
135 const char *f;
136
137 // Large ratios can happen because of overlays that are
138 // appended after a program is packed.
139 unsigned ratio = get_ratio(fu_len, fc_len);
140 if (ratio >= 1000 * 1000)
141 strcpy(r, "overlay");
142 else
143 upx_snprintf(r, sizeof(r), "%3u.%02u%%", ratio / 10000, (ratio % 10000) / 100);
144 if (decompress)
145 f = "%10lld <-%10lld %7s %15s %s";
146 else
147 f = "%10lld ->%10lld %7s %15s %s";
148 center_string(fn, sizeof(fn), format_name);
149 assert(strlen(fn) == 15);
150 upx_snprintf(buf, sizeof(buf), f, (long long) fu_len, (long long) fc_len, r, fn, filename);
151 UNUSED(u_len);
152 UNUSED(c_len);
153 return buf;
154 }
155
156 /*************************************************************************
157 //
158 **************************************************************************/
159
UiPacker(const Packer * p_)160 UiPacker::UiPacker(const Packer *p_) : ui_pass(0), ui_total_passes(0), p(p_), s(NULL) {
161 init_global_constants();
162
163 cb.reset();
164
165 s = new State;
166 memset(s, 0, sizeof(*s));
167 s->msg_buf[0] = '\r';
168
169 #if defined(UI_USE_SCREEN)
170 // FIXME - ugly hack
171 s->screen = sobject_get_screen();
172 #endif
173
174 if (opt->verbose < 0)
175 s->mode = M_QUIET;
176 else if (opt->verbose == 0 || !acc_isatty(STDOUT_FILENO))
177 s->mode = M_INFO;
178 else if (opt->verbose == 1 || opt->no_progress)
179 s->mode = M_MSG;
180 else if (s->screen == NULL)
181 s->mode = M_CB_TERM;
182 else
183 s->mode = M_CB_SCREEN;
184 }
185
~UiPacker()186 UiPacker::~UiPacker() {
187 cb.reset();
188 delete s;
189 s = NULL;
190 }
191
192 /*************************************************************************
193 // start callback
194 **************************************************************************/
195
printInfo(int nl)196 void UiPacker::printInfo(int nl) {
197 if (opt->all_methods && s->total_passes > 1)
198 con_fprintf(stdout, "Compressing %s [%s]%s", p->fi->getName(), p->getName(),
199 nl ? "\n" : "");
200 else {
201 char method_name[32 + 1];
202 set_method_name(method_name, sizeof(method_name), p->ph.method, p->ph.level);
203 con_fprintf(stdout, "Compressing %s [%s, %s]%s", p->fi->getName(), p->getName(),
204 method_name, nl ? "\n" : "");
205 }
206 }
207
startCallback(unsigned u_len,unsigned step,int pass,int total_passes)208 void UiPacker::startCallback(unsigned u_len, unsigned step, int pass, int total_passes) {
209 s->u_len = u_len;
210 s->step = step;
211 s->next_update = step;
212
213 s->pass = pass;
214 s->total_passes = total_passes;
215 // printf("startCallback %d %d\n", s->pass, s->total_passes);
216
217 s->bar_len = 64;
218 s->pos = -2;
219 s->spin_counter = 0;
220 s->bar_pos = 1; // because of the leading '\r'
221 s->pass_digits = 0;
222
223 cb.reset();
224
225 if (s->pass < 0) // no callback wanted
226 return;
227
228 if (s->mode <= M_INFO)
229 return;
230 if (s->mode == M_MSG) {
231 if (pass <= 1) {
232 printInfo(0);
233 fflush(stdout);
234 printSetNl(2);
235 }
236 return;
237 }
238
239 cb.nprogress = progress_callback;
240 cb.user = this; // parameter for static function UiPacker::progress_callback()
241
242 if (s->mode == M_CB_TERM) {
243 const char *fname = fn_basename(p->fi->getName());
244 int l = (int) strlen(fname);
245 if (l > 0 && l <= 30) {
246 strcpy(&s->msg_buf[s->bar_pos], fname);
247 s->bar_pos += l;
248 s->msg_buf[s->bar_pos++] = ' ';
249 s->msg_buf[s->bar_pos++] = ' ';
250 s->bar_len -= l + 2;
251 }
252 }
253
254 // set pass
255 if (total_passes > 1) {
256 int buflen, l;
257 do {
258 s->pass_digits++;
259 total_passes /= 10;
260 } while (total_passes > 0);
261 buflen = sizeof(s->msg_buf) - s->bar_pos;
262 l = upx_snprintf(&s->msg_buf[s->bar_pos], buflen, "%*d/%*d ", s->pass_digits, s->pass,
263 s->pass_digits, s->total_passes);
264 if (l > 0 && s->bar_len - l > 10) {
265 s->bar_len -= l;
266 s->bar_pos += l;
267 }
268 }
269
270 #if (UI_USE_SCREEN)
271 if (s->mode == M_CB_SCREEN) {
272 if (!s->screen_init_done) {
273 s->screen_init_done = 1;
274 if (s->screen->hideCursor)
275 s->cursor_shape = s->screen->hideCursor(s->screen);
276 s->s_fg = s->screen->getFg(s->screen);
277 s->s_bg = s->screen->getBg(s->screen);
278 s->screen->getCursor(s->screen, &s->s_cx, &s->s_cy);
279 s->scroll_up = s->screen->getScrollCounter(s->screen);
280 printInfo(1);
281 s->screen->getCursor(s->screen, &s->b_cx, &s->b_cy);
282 s->scroll_up = s->screen->getScrollCounter(s->screen) - s->scroll_up;
283 }
284 }
285 #endif /* UI_USE_SCREEN */
286 }
287
288 // may only get called directly after startCallback()
firstCallback()289 void UiPacker::firstCallback() {
290 if (s->pos == -2)
291 doCallback(0, 0);
292 }
293
294 // make sure we reach 100% in the progress bar
finalCallback(unsigned u_len,unsigned c_len)295 void UiPacker::finalCallback(unsigned u_len, unsigned c_len) {
296 s->next_update = u_len;
297 doCallback(u_len, c_len);
298 }
299
300 /*************************************************************************
301 // end callback
302 **************************************************************************/
303
endCallback()304 void UiPacker::endCallback() {
305 bool done = (s->total_passes <= 0 || s->pass >= s->total_passes);
306 endCallback(done);
307 }
308
endCallback(bool done)309 void UiPacker::endCallback(bool done) {
310 if (s->pass < 0) // no callback wanted
311 return;
312
313 if (s->mode == M_CB_TERM) {
314 if (done)
315 printClearLine(stdout);
316 else
317 printSetNl(2);
318 }
319
320 // restore screen
321 #if (UI_USE_SCREEN)
322 if (s->mode == M_CB_SCREEN) {
323 if (done) {
324 int cx, cy, sy;
325 assert(s->screen_init_done);
326 s->screen_init_done = 0;
327 assert(s->s_cx == 0 && s->b_cx == 0);
328 s->screen->getCursor(s->screen, &cx, &cy);
329 sy = UPX_MAX(0, s->s_cy - s->scroll_up);
330 while (cy >= sy)
331 s->screen->clearLine(s->screen, cy--);
332 s->screen->setCursor(s->screen, s->s_cx, sy);
333 s->screen->setFg(s->screen, s->s_fg);
334 s->screen->setBg(s->screen, s->s_bg);
335 if (s->cursor_shape > 0)
336 s->screen->setCursorShape(s->screen, s->cursor_shape);
337 } else {
338 // not needed:
339 // s->screen->clearLine(s->screen, s->b_cy);
340 // s->screen->setCursor(s->screen, s->b_cx, s->b_cy);
341 }
342 }
343 #endif /* UI_USE_SCREEN */
344
345 cb.reset();
346 #if 0
347 printf("callback: pass %d, step %6d, updates %6d\n",
348 s->pass, s->step, s->spin_counter);
349 #endif
350 }
351
352 /*************************************************************************
353 // the callback
354 **************************************************************************/
355
progress_callback(upx_callback_p cb,unsigned isize,unsigned osize)356 void __acc_cdecl UiPacker::progress_callback(upx_callback_p cb, unsigned isize, unsigned osize) {
357 // printf("%6d %6d %d\n", isize, osize, state);
358 UiPacker *self = (UiPacker *) cb->user;
359 self->doCallback(isize, osize);
360 }
361
doCallback(unsigned isize,unsigned osize)362 void UiPacker::doCallback(unsigned isize, unsigned osize) {
363 int i;
364 static const char spinner[] = "|/-\\";
365
366 if (s->pass < 0) // no callback wanted
367 return;
368
369 if (s->u_len == 0 || isize > s->u_len)
370 return;
371 // check if we should update the display
372 if (s->step > 0 && isize > 0 && isize < s->u_len) {
373 if (isize < s->next_update)
374 return;
375 s->next_update += s->step;
376 }
377
378 // compute progress position
379 int pos = -1;
380 if (isize >= s->u_len)
381 pos = s->bar_len;
382 else if (isize > 0) {
383 pos = get_ratio(s->u_len, isize) * s->bar_len / 1000000;
384 assert(pos >= 0);
385 assert(pos <= s->bar_len);
386 }
387
388 #if 0
389 printf("%6d %6d %6d %6d %3d %3d\n", isize, osize, s->step, s->next_update, pos, s->pos);
390 return;
391 #endif
392
393 if (pos < s->pos)
394 return;
395 if (pos < 0 && pos == s->pos)
396 return;
397
398 // fill the progress bar
399 char *m = &s->msg_buf[s->bar_pos];
400 *m++ = progress_filler[2];
401 for (i = 0; i < s->bar_len; i++)
402 *m++ = progress_filler[i <= pos];
403 *m++ = progress_filler[3];
404
405 // compute current compression ratio
406 unsigned ratio = 1000000;
407 if (osize > 0)
408 ratio = get_ratio(isize, osize);
409
410 int buflen = (int) (&s->msg_buf[sizeof(s->msg_buf)] - m);
411 upx_snprintf(m, buflen, " %3d.%1d%% %c ", ratio / 10000, (ratio % 10000) / 1000,
412 spinner[s->spin_counter & 3]);
413 assert(strlen(s->msg_buf) < 1 + 80);
414
415 s->pos = pos;
416 s->spin_counter++;
417
418 if (s->mode == M_CB_TERM) {
419 const char *msg = &s->msg_buf[0];
420 int fg = con_fg(stdout, FG_CYAN);
421 con_fprintf(stdout, "%s", msg); // avoid backslash interpretation
422 (void) con_fg(stdout, fg);
423 fflush(stdout);
424 printSetNl(1);
425 UNUSED(fg);
426 return;
427 }
428
429 #if (UI_USE_SCREEN)
430 if (s->mode == M_CB_SCREEN) {
431 const char *msg = &s->msg_buf[1];
432 #if 0
433 s->screen->putString(s->screen,msg,s->b_cx,s->b_cy);
434 #else
435 // FIXME: this doesn't honor '--mono' etc.
436 int attr = FG_CYAN | s->s_bg;
437 s->screen->putStringAttr(s->screen, msg, attr, s->b_cx, s->b_cy);
438 #endif
439 s->screen->refresh(s->screen);
440 }
441 #endif /* UI_USE_SCREEN */
442 }
443
444 /*************************************************************************
445 // pack
446 **************************************************************************/
447
uiPackStart(const OutputFile * fo)448 void UiPacker::uiPackStart(const OutputFile *fo) {
449 total_files++;
450 UNUSED(fo);
451 }
452
uiPackEnd(const OutputFile * fo)453 void UiPacker::uiPackEnd(const OutputFile *fo) {
454 uiUpdate(fo->st_size());
455
456 if (s->mode == M_QUIET)
457 return;
458 if (s->mode == M_MSG) {
459 // We must put this here and not in endCallback() as we may
460 // have multiple passes.
461 printClearLine(stdout);
462 }
463
464 const char *name = p->fi->getName();
465 if (opt->output_name)
466 name = opt->output_name;
467 else if (opt->to_stdout)
468 name = "<stdout>";
469 con_fprintf(stdout, "%s\n", mkline(p->ph.u_file_size, fo->st_size(), p->ph.u_len, p->ph.c_len,
470 p->getName(), fn_basename(name)));
471 printSetNl(0);
472 }
473
uiPackTotal()474 void UiPacker::uiPackTotal() {
475 uiListTotal();
476 uiFooter("Packed");
477 }
478
479 /*************************************************************************
480 // unpack
481 **************************************************************************/
482
uiUnpackStart(const OutputFile * fo)483 void UiPacker::uiUnpackStart(const OutputFile *fo) {
484 total_files++;
485 UNUSED(fo);
486 }
487
uiUnpackEnd(const OutputFile * fo)488 void UiPacker::uiUnpackEnd(const OutputFile *fo) {
489 uiUpdate(-1, fo->getBytesWritten());
490
491 if (s->mode == M_QUIET)
492 return;
493
494 const char *name = p->fi->getName();
495 if (opt->output_name)
496 name = opt->output_name;
497 else if (opt->to_stdout)
498 name = "<stdout>";
499 con_fprintf(stdout, "%s\n", mkline(fo->getBytesWritten(), p->file_size, p->ph.u_len,
500 p->ph.c_len, p->getName(), fn_basename(name), true));
501 printSetNl(0);
502 }
503
uiUnpackTotal()504 void UiPacker::uiUnpackTotal() {
505 uiListTotal(true);
506 uiFooter("Unpacked");
507 }
508
509 /*************************************************************************
510 // list
511 **************************************************************************/
512
uiListStart()513 void UiPacker::uiListStart() { total_files++; }
514
uiList()515 void UiPacker::uiList() {
516 const char *name = p->fi->getName();
517 con_fprintf(stdout, "%s\n", mkline(p->ph.u_file_size, p->file_size, p->ph.u_len, p->ph.c_len,
518 p->getName(), name));
519 printSetNl(0);
520 }
521
uiListEnd()522 void UiPacker::uiListEnd() { uiUpdate(); }
523
uiListTotal(bool decompress)524 void UiPacker::uiListTotal(bool decompress) {
525 if (opt->verbose >= 1 && total_files >= 2) {
526 char name[32];
527 upx_snprintf(name, sizeof(name), "[ %u file%s ]", total_files_done,
528 total_files_done == 1 ? "" : "s");
529 con_fprintf(stdout, "%s%s\n", header_line2, mkline(total_fu_len, total_fc_len, total_u_len,
530 total_c_len, "", name, decompress));
531 printSetNl(0);
532 }
533 }
534
535 /*************************************************************************
536 // test
537 **************************************************************************/
538
uiTestStart()539 void UiPacker::uiTestStart() {
540 total_files++;
541
542 if (opt->verbose >= 1) {
543 con_fprintf(stdout, "testing %s ", p->fi->getName());
544 fflush(stdout);
545 printSetNl(1);
546 }
547 }
548
uiTestEnd()549 void UiPacker::uiTestEnd() {
550 if (opt->verbose >= 1) {
551 con_fprintf(stdout, "[OK]\n");
552 fflush(stdout);
553 printSetNl(0);
554 }
555 uiUpdate();
556 }
557
uiTestTotal()558 void UiPacker::uiTestTotal() { uiFooter("Tested"); }
559
560 /*************************************************************************
561 // info
562 **************************************************************************/
563
uiFileInfoStart()564 bool UiPacker::uiFileInfoStart() {
565 #if defined(_WIN32) // msvcrt
566 #define PRLLD "I64d"
567 #else
568 #define PRLLD "lld"
569 #endif
570 total_files++;
571
572 int fg = con_fg(stdout, FG_CYAN);
573 con_fprintf(stdout, "%s [%s, %s]\n", p->fi->getName(), p->getFullName(opt), p->getName());
574 fg = con_fg(stdout, fg);
575 UNUSED(fg);
576 if (p->ph.c_len > 0) {
577 con_fprintf(stdout, " %8" PRLLD " bytes", (long long) p->file_size);
578 con_fprintf(stdout, ", compressed by UPX %d, method %d, level %d, filter 0x%02x/0x%02x\n",
579 p->ph.version, p->ph.method, p->ph.level, p->ph.filter, p->ph.filter_cto);
580 return false;
581 } else {
582 con_fprintf(stdout, " %8" PRLLD " bytes", (long long) p->file_size);
583 con_fprintf(stdout, ", not compressed by UPX\n");
584 return true;
585 }
586 #undef PRLLD
587 }
588
uiFileInfoEnd()589 void UiPacker::uiFileInfoEnd() { uiUpdate(); }
590
uiFileInfoTotal()591 void UiPacker::uiFileInfoTotal() {}
592
593 /*************************************************************************
594 // util
595 **************************************************************************/
596
uiHeader()597 void UiPacker::uiHeader() {
598 static bool done = false;
599 if (done)
600 return;
601 done = true;
602 if (opt->cmd == CMD_TEST || opt->cmd == CMD_FILEINFO)
603 return;
604 if (opt->verbose >= 1) {
605 con_fprintf(stdout, "%s%s", header_line1, header_line2);
606 }
607 }
608
uiFooter(const char * t)609 void UiPacker::uiFooter(const char *t) {
610 static bool done = false;
611 if (done)
612 return;
613 done = true;
614 if (opt->verbose >= 1) {
615 assert(total_files >= total_files_done);
616 unsigned n1 = total_files;
617 unsigned n2 = total_files_done;
618 unsigned n3 = total_files - total_files_done;
619 if (n3 == 0)
620 con_fprintf(stdout, "\n%s %u file%s.\n", t, n1, n1 == 1 ? "" : "s");
621 else
622 con_fprintf(stdout, "\n%s %u file%s: %u ok, %u error%s.\n", t, n1, n1 == 1 ? "" : "s",
623 n2, n3, n3 == 1 ? "" : "s");
624 }
625 }
626
uiUpdate(off_t fc_len,off_t fu_len)627 void UiPacker::uiUpdate(off_t fc_len, off_t fu_len) {
628 update_fc_len = (fc_len >= 0) ? fc_len : p->file_size;
629 update_fu_len = (fu_len >= 0) ? fu_len : p->ph.u_file_size;
630 update_c_len = p->ph.c_len;
631 update_u_len = p->ph.u_len;
632 }
633
uiConfirmUpdate()634 void UiPacker::uiConfirmUpdate() {
635 total_files_done++;
636 total_fc_len += update_fc_len;
637 total_fu_len += update_fu_len;
638 total_c_len += update_c_len;
639 total_u_len += update_u_len;
640 }
641
642 /* vim:set ts=4 sw=4 et: */
643