/* * Copyright (c) 2017 Brian Barto * * This program is free software; you can redistribute it and/or modify it * under the terms of the MIT License. See LICENSE for more details. */ /* * DESCRIPTION * * This library encapsulates the functionality required to produce the * famous data decryption effect from the 1992 hacker movie Sneakers. */ #define _XOPEN_SOURCE 700 #include #include #include #include #include #include #include #include #include #include #include #include #include "nms.h" // Color identifiers /* #define COLOR_BLACK 0 #define COLOR_RED 1 #define COLOR_GREEN 2 #define COLOR_YELLOW 3 #define COLOR_BLUE 4 #define COLOR_MAGENTA 5 #define COLOR_CYAN 6 #define COLOR_WHITE 7 */ // Macros for VT100 codes /* #define CLEAR_SCR() printf("\033[2J") // Clear Screen #define CURSOR_HOME() printf("\033[H") // Move cursor to home position (0,0) #define CURSOR_MOVE(x,y) printf("\033[%i;%iH", x, y) // Move cursor to x,y #define BEEP() printf("\a"); // terminal bell #define BOLD() printf("\033[1m") // Cursor bold #define FOREGROUND_COLOR(x) printf("\033[3%im", x) // Set foreground color #define CLEAR_ATTR() printf("\033[0m") // Clear bold/color attributes #define SCREEN_SAVE() printf("\033[?47h") // Save screen display #define SCREEN_RESTORE() printf("\033[?47l") // Restore screen to previously saved state #define CURSOR_SAVE() printf("\033[s") // Save cursor position #define CURSOR_RESTORE() printf("\033[u") // Restore cursor position #define CURSOR_HIDE() printf("\033[?25l") // Hide cursor #define CURSOR_SHOW() printf("\033[?25h") // Unhide cursor */ // Program settings #define TYPE_EFFECT_SPEED 4 // miliseconds per char #define JUMBLE_SECONDS 2 // number of seconds for jumble effect #define JUMBLE_LOOP_SPEED 35 // miliseconds between each jumble #define REVEAL_LOOP_SPEED 50 // miliseconds between each reveal loop #define MASK_CHAR_COUNT 218 // Total characters in maskCharTable[] array. // Character attribute structure, linked list. Keeps track of every // character's attributes required for rendering and decryption effect. struct charAttr { char *source; char *mask; int width; int is_space; int time; struct charAttr *next; }; // Static function prototypes static void nms_sleep(int); // NMS settings static int foregroundColor = COLOR_BLUE; // Foreground color setting static int autoDecrypt = 0; // Auto-decrypt flag static char *returnOpts = NULL; // Return option setting static int inputPositionX = -1; // X coordinate for input position static int inputPositionY = -1; // Y coordinate for input position // Character table representing the character set know as CP437 used by // the original IBM PC - https://en.wikipedia.org/wiki/Code_page_437 static char *maskCharTable[] = { "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", "~", ".", "/", ":", ";", "<", "=", ">", "?", "[", "\\", "]", "_", "{", "}", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "\xc3\x87", "\xc3\xbc", "\xc3\xa9", "\xc3\xa2", "\xc3\xa4", "\xc3\xa0", "\xc3\xa5", "\xc3\xa7", "\xc3\xaa", "\xc3\xab", "\xc3\xa8", "\xc3\xaf", "\xc3\xae", "\xc3\xac", "\xc3\x84", "\xc3\x85", "\xc3\x89", "\xc3\xa6", "\xc3\x86", "\xc3\xb4", "\xc3\xb6", "\xc3\xb2", "\xc3\xbb", "\xc3\xb9", "\xc3\xbf", "\xc3\x96", "\xc3\x9c", "\xc2\xa2", "\xc2\xa3", "\xc2\xa5", "\xc6\x92", "\xc3\xa1", "\xc3\xad", "\xc3\xb3", "\xc3\xba", "\xc3\xb1", "\xc3\x91", "\xc2\xaa", "\xc2\xba", "\xc2\xbf", "\xc2\xac", "\xc2\xbd", "\xc2\xbc", "\xc2\xa1", "\xc2\xab", "\xc2\xbb", "\xce\xb1", "\xc3\x9f", "\xce\x93", "\xcf\x80", "\xce\xa3", "\xcf\x83", "\xc2\xb5", "\xcf\x84", "\xce\xa6", "\xce\x98", "\xce\xa9", "\xce\xb4", "\xcf\x86", "\xce\xb5", "\xc2\xb1", "\xc3\xb7", "\xc2\xb0", "\xc2\xb7", "\xc2\xb2", "\xc2\xb6", "\xe2\x8c\x90", "\xe2\x82\xa7", "\xe2\x96\x91", "\xe2\x96\x92", "\xe2\x96\x93", "\xe2\x94\x82", "\xe2\x94\xa4", "\xe2\x95\xa1", "\xe2\x95\xa2", "\xe2\x95\x96", "\xe2\x95\x95", "\xe2\x95\xa3", "\xe2\x95\x91", "\xe2\x95\x97", "\xe2\x95\x9d", "\xe2\x95\x9c", "\xe2\x95\x9b", "\xe2\x94\x90", "\xe2\x94\x94", "\xe2\x94\xb4", "\xe2\x94\xac", "\xe2\x94\x9c", "\xe2\x94\x80", "\xe2\x94\xbc", "\xe2\x95\x9e", "\xe2\x95\x9f", "\xe2\x95\x9a", "\xe2\x95\x94", "\xe2\x95\xa9", "\xe2\x95\xa6", "\xe2\x95\xa0", "\xe2\x95\x90", "\xe2\x95\xac", "\xe2\x95\xa7", "\xe2\x95\xa8", "\xe2\x95\xa4", "\xe2\x95\xa7", "\xe2\x95\x99", "\xe2\x95\x98", "\xe2\x95\x92", "\xe2\x95\x93", "\xe2\x95\xab", "\xe2\x95\xaa", "\xe2\x94\x98", "\xe2\x94\x8c", "\xe2\x96\x88", "\xe2\x96\x84", "\xe2\x96\x8c", "\xe2\x96\x90", "\xe2\x96\x80", "\xe2\x88\x9e", "\xe2\x88\xa9", "\xe2\x89\xa1", "\xe2\x89\xa5", "\xe2\x89\xa4", "\xe2\x8c\xa0", "\xe2\x8c\xa1", "\xe2\x89\x88", "\xe2\x88\x99", "\xe2\x88\x9a", "\xe2\x81\xbf", "\xe2\x96\xa0" }; /* * nms_exec() - This function is passed a pointer to a character string * and displays the contents of the string in a way that mimicks the * "decrypting text" effect in the 1992 movie Sneakers. It returns the * last character pressed by the user. */ char nms_exec(char *string) { struct charAttr *list_pointer = NULL; struct charAttr *list_head = NULL; struct charAttr *list_temp = NULL; int i, revealed = 0; int maxRows, maxCols, curRow = 0, curCol = 0; char ret = 0; // Error if we have an empty string. if (string == NULL || string[0] == '\0') { fprintf(stderr, "Error. Empty string.\n"); return 0; } // Reassociate STDIN to the terminal is needed if (!isatty(STDIN_FILENO) && !freopen ("/dev/tty", "r", stdin)) { fprintf(stderr, "Error. Can't associate STDIN with terminal.\n"); return 0; } // Needed for UTF-8 support setlocale(LC_ALL, ""); // Initialize terminal and settings initscr(); cbreak(); noecho(); curs_set(0); move(0, 0); if (has_colors()) { start_color(); init_pair(1, foregroundColor, COLOR_BLACK); } // Get terminal window size getmaxyx(stdscr, maxRows, maxCols); // Seed my random number generator with the current time srand(time(NULL)); // Geting input for (i = 0; string[i] != '\0'; ++i) { // Don't go beyond maxRows if (curRow >= maxRows - 1) { break; } // Allocate memory for next list link if (list_pointer == NULL) { list_pointer = malloc(sizeof(struct charAttr)); list_head = list_pointer; } else { list_pointer->next = malloc(sizeof(struct charAttr)); list_pointer = list_pointer->next; } // Get character's byte-length and store character. if (mblen(&string[i], 4) > 0) { list_pointer->source = malloc(mblen(&string[i], 4) + 1); strncpy(list_pointer->source, &string[i], mblen(&string[i], 4)); list_pointer->source[mblen(&string[i], 4)] = '\0'; i += (mblen(&string[i], 4) - 1); } else { endwin(); fprintf(stderr, "Unknown character encountered. Quitting.\n"); return 0; } // Set flag if we have a whitespace character if (strlen(list_pointer->source) == 1 && isspace(list_pointer->source[0])) list_pointer->is_space = 1; else list_pointer->is_space = 0; // Set initial mask chharacter list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; // Set reveal time list_pointer->time = rand() % 5000; // Set character column width wchar_t widec[sizeof(list_pointer->source)] = {}; mbstowcs(widec, list_pointer->source, sizeof(list_pointer->source)); list_pointer->width = wcwidth(*widec); // Set next node to null list_pointer->next = NULL; // Track row count if (string[i] == '\n' || (curCol += list_pointer->width) > maxCols) { curCol = 0; curRow++; } } // Print mask characters with 'type effect' for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { // Print mask character (or space) if (list_pointer->is_space) { addstr(list_pointer->source); continue; } // print mask character addstr(list_pointer->mask); if (list_pointer->width == 2) { addstr(maskCharTable[rand() % MASK_CHAR_COUNT]); } // flush output and sleep refresh(); nms_sleep(TYPE_EFFECT_SPEED); } // Flush any input up to this point flushinp(); // If autoDecrypt flag is set, we sleep. Otherwise require user to // press a key to continue. if (autoDecrypt) sleep(1); else getch(); // Jumble loop for (i = 0; i < (JUMBLE_SECONDS * 1000) / JUMBLE_LOOP_SPEED; ++i) { // Move cursor to home position move(0, 0); // Print new mask for all characters for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { // Print mask character (or space) if (list_pointer->is_space) { addstr(list_pointer->source); continue; } // print new mask character addstr(maskCharTable[rand() % MASK_CHAR_COUNT]); if (list_pointer->width == 2) { addstr(maskCharTable[rand() % MASK_CHAR_COUNT]); } } // flush output and sleep refresh(); nms_sleep(JUMBLE_LOOP_SPEED); } // Reveal loop while (!revealed) { // Move cursor to home position move(0, 0); // Set revealed flag revealed = 1; for (list_pointer = list_head; list_pointer != NULL; list_pointer = list_pointer->next) { // Print mask character (or space) if (list_pointer->is_space) { addstr(list_pointer->source); continue; } // If we still have time before the char is revealed, display the mask if (list_pointer->time > 0) { // Change the mask randomly if (list_pointer->time < 500) { if (rand() % 3 == 0) { list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; } } else { if (rand() % 10 == 0) { list_pointer->mask = maskCharTable[rand() % MASK_CHAR_COUNT]; } } // Print mask addstr(list_pointer->mask); // Decrement reveal time list_pointer->time -= REVEAL_LOOP_SPEED; // Unset revealed flag revealed = 0; } else { // Set bold and foreground color for character reveal attron(A_BOLD); if (has_colors()) attron(COLOR_PAIR(1)); // print source character addstr(list_pointer->source); // Unset foreground color if (has_colors()) attroff(COLOR_PAIR(1)); attroff(A_BOLD); } } // flush output and sleep refresh(); nms_sleep(REVEAL_LOOP_SPEED); } // Flush any input up to this point flushinp(); // Check if user must select from a set of options if (returnOpts != NULL && strlen(returnOpts) > 0) { // Position cursor if necessary if (inputPositionY >= 0 && inputPositionX >= 0) { move(inputPositionY, inputPositionX); } // Turn on cursor curs_set(1); refresh(); // Get and validate user selection while (strchr(returnOpts, ret = getch()) == NULL) { beep(); } } // User must press a key to continue when clearSrc is set // without returnOpts else { getch(); } // End curses mode endwin(); // Freeing the list. list_pointer = list_head; while (list_pointer != NULL) { list_temp = list_pointer; list_pointer = list_pointer->next; free(list_temp->source); free(list_temp); } return ret; } /* * nms_set_foreground_color() sets the foreground color of the unencrypted * characters as they are revealed to the color indicated by the 'color' * argument. Valid arguments are "white", "yellow", "magenta", "blue", * "green", "red", and "cyan". This function will default to blue if * passed an invalid color. No value is returned. */ void nms_set_foreground_color(char *color) { if(strcmp("white", color) == 0) foregroundColor = COLOR_WHITE; else if(strcmp("yellow", color) == 0) foregroundColor = COLOR_YELLOW; else if(strcmp("black", color) == 0) foregroundColor = COLOR_BLACK; else if(strcmp("magenta", color) == 0) foregroundColor = COLOR_MAGENTA; else if(strcmp("blue", color) == 0) foregroundColor = COLOR_BLUE; else if(strcmp("green", color) == 0) foregroundColor = COLOR_GREEN; else if(strcmp("red", color) == 0) foregroundColor = COLOR_RED; else if(strcmp("cyan", color) == 0) foregroundColor = COLOR_CYAN; else foregroundColor = COLOR_BLUE; } /* * nms_set_auto_decrypt() sets the autoDecrypt flag according to the * true/false value of the 'setting' argument. */ void nms_set_auto_decrypt(int setting) { if (setting) autoDecrypt = 1; else autoDecrypt = 0; } /* * nms_set_clear_scr() sets the clearScr flag according to the * true/false value of the 'setting' argument. NOTE: FOR NCURSES THIS * IS NOT USED. */ void nms_set_clear_scr(int setting) { (void) setting; return; } /* * nms_set_input_position() sets the desired coordinate of the cursor in * the terminal when accepting user input after nms_exec() reveals the * unencrypted characters. */ void nms_set_input_position(int x, int y) { if (x >= 0 && y >= 0) { inputPositionX = x; inputPositionY = y; } } /* * nms_sleep() sleeps for the number of miliseconds indicated by 't'. */ static void nms_sleep(int t) { struct timespec ts; ts.tv_sec = t / 1000; ts.tv_nsec = (t % 1000) * 1000000; nanosleep(&ts, NULL); }