wordplay.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. #include "charman.h"
  2. #include "images.h"
  3. #include "lastseen.h"
  4. #include "logs_utils.h"
  5. #include "render.h"
  6. #include "terminal.h"
  7. #include "utils.h"
  8. #include "zf_log.h"
  9. #include <ctype.h>
  10. #include <iomanip>
  11. #include <regex>
  12. #include <sstream>
  13. #include <string.h>
  14. #include <string>
  15. #include <unistd.h> // write
  16. #include <vector>
  17. extern struct console_details console;
  18. extern std::string username;
  19. extern std::string fullname;
  20. #define BSIZE 512
  21. void a_or_an(std::string &word) {
  22. /* If it starts with a vowel "an" */
  23. char ch = toupper(word[0]);
  24. if ((ch == 'A') || (ch == 'E') || (ch == 'I') || (ch == 'O') || (ch == 'U')) {
  25. word.insert(0, " an ");
  26. } else {
  27. word.insert(0, " a ");
  28. }
  29. }
  30. /*
  31. * harry_idle_event(fd)
  32. *
  33. * User is idle, let's let them know we're here. HAHAHA!
  34. *
  35. */
  36. void harry_idle_event(int fd) {
  37. // Make something happen
  38. std::ostringstream buffer;
  39. int r;
  40. int level = harry_level();
  41. if (!level)
  42. return;
  43. // If the username is something, we have their info!
  44. bool have_userinfo = !username.empty();
  45. int xpos = console.posx;
  46. // This is no where near finished, BUT!
  47. // Do not put any ^ codes in these -- the strlen() would be wrong.
  48. const char *phrases[] = {
  49. "Hahaha",
  50. "Snicker, snicker",
  51. "Boo!",
  52. "Arrooo!",
  53. "Ahh-wooo!",
  54. "Aaaooo!",
  55. "Sorry, I forgot!",
  56. "The Matrix has you...",
  57. "Follow the white rabbit.",
  58. };
  59. const int total_phrases = sizeof(phrases) / sizeof(char *);
  60. const char *user_phrases[] = {
  61. "Is USER really here?",
  62. "Knock, Knock NICK.",
  63. "What is a NICK?",
  64. "Wake up, NICK...",
  65. "Here lies USER, rest in peace.",
  66. "Don't be scared, NICK.",
  67. };
  68. const int total_user_phrases = sizeof(user_phrases) / sizeof(char *);
  69. // I'd like to use total_possible, but ... it isn't possible. (NNY)
  70. static LastSeen last_seen_harry_event(LastSeen::best_guess(total_phrases));
  71. int total_possible =
  72. have_userinfo ? total_phrases + total_user_phrases : total_phrases;
  73. do {
  74. r = randint(total_possible);
  75. } while (last_seen_harry_event.seen_before(r));
  76. ZF_LOGD("have %d picked %d total %d console @ %d,%d", have_userinfo, r,
  77. total_possible, console.posx, console.posy);
  78. std::string selected;
  79. if (r >= total_phrases) {
  80. selected = user_phrases[r - total_phrases];
  81. std::string temp = username;
  82. a_or_an(temp);
  83. replace(selected, " a NICK", temp);
  84. replace(selected, "USER", fullname);
  85. replace(selected, "NICK", username);
  86. } else
  87. selected = phrases[r];
  88. ZF_LOGD("Selected: %s", selected.c_str());
  89. if (selected.size() + xpos > 78) {
  90. ZF_LOGD("Sorry, too long (%d)", (int)selected.size() + xpos);
  91. } else {
  92. int color = randint(15) + 1;
  93. int pause = 2;
  94. if (selected.size() > 20) {
  95. pause = 5;
  96. }
  97. // %02d = std::setfill('0') << std::setw(2) << (int)
  98. /*
  99. buffer << "^CS^S2^C" << std::setfill('0') << std::setw(2) << color
  100. << phrases[r] << "^P2^CR^D" << std::setw(2) << strlen(phrases[r]);
  101. */
  102. buffer << TRIGGER "CS" TRIGGER "S2" TRIGGER "C" << std::setfill('0')
  103. << std::setw(2) << color
  104. << selected
  105. // Reset SPEED and RENDER
  106. << TRIGGER "S0" TRIGGER "R0" TRIGGER "P" << pause
  107. << TRIGGER "CR" TRIGGER "D" << std::setw(2) << selected.size();
  108. std::string str = buffer.str();
  109. ZF_LOGD("harry_event: render(%d, \"%s\")", fd, str.c_str());
  110. render(fd, str);
  111. }
  112. }
  113. void init_harry() {
  114. // init_have_seen(last_seen_harry_event, MAX_HARRY_EVENT_DUPS);
  115. // ZF_LOGD("init => %d %d", last_seen_harry_event[0],
  116. // last_seen_harry_event[1]);
  117. console_init(&console);
  118. reset_render();
  119. }
  120. // char words[] = "[a-zA-Z]+( [a-zA-Z]+)+";
  121. int mangle_clrscr(std::string &buffer, std::string &work, size_t pos) {
  122. static int ANSI_CLS_count = 0;
  123. ZF_LOGI("seen: ANSI_CLS");
  124. ANSI_CLS_count++;
  125. int level = harry_level();
  126. if (!level)
  127. return 0;
  128. // Don't activate on the first ANSI_CLS (that's the BBS Version display/login)
  129. if (ANSI_CLS_count > 1) {
  130. // if (random_activate((level + 1) / 2)) {
  131. if (random_activate(level * 5)) {
  132. std::ostringstream display;
  133. int needs_cls = 0;
  134. struct image {
  135. const char **lines;
  136. int size;
  137. int cls;
  138. int width; // height = size
  139. } images[] = {{ghost, sizeof(ghost) / sizeof(char *), 1, 0},
  140. {ghead, sizeof(ghead) / sizeof(char *), 1, 0},
  141. {wolf, sizeof(wolf) / sizeof(char *), 1, 0},
  142. {panther, sizeof(panther) / sizeof(char *), 1, 0},
  143. {bat, sizeof(bat) / sizeof(char *), 1, 0},
  144. {icu, sizeof(icu) / sizeof(char *), 0, 20},
  145. {skull, sizeof(skull) / sizeof(char *), 0, 19},
  146. {skullblink, sizeof(skullblink) / sizeof(char *), 0, 19},
  147. // {specter, sizeof(specter) / sizeof(char *), 1, 0},
  148. {owl, sizeof(owl) / sizeof(char *), 0, 70}
  149. };
  150. const int total_images = sizeof(images) / sizeof(image);
  151. static LastSeen last_files(LastSeen::best_guess(total_images));
  152. int r;
  153. do {
  154. r = randint(total_images);
  155. } while (last_files.seen_before(r));
  156. std::string fgoto;
  157. if (!images[r].cls) {
  158. int x = 0, y = 0;
  159. x = randint(79 - images[r].width) + 1;
  160. y = randint(24 - images[r].size) + 1;
  161. display << TRIGGER "CS" TRIGGER "f" << std::setfill('0') << std::setw(2)
  162. << x << std::setw(2) << y << "\x1b[1;1H";
  163. fgoto = display.str();
  164. // reset display
  165. display.str(std::string());
  166. display.clear();
  167. } else {
  168. if (images[r].lines == specter) {
  169. // specter!
  170. fgoto.assign(TRIGGER "S1" TRIGGER "CS" TRIGGER "F");
  171. } else
  172. fgoto.assign(TRIGGER "CS" TRIGGER "F");
  173. }
  174. needs_cls = images[r].cls;
  175. // I get what's happening. Mystic moves cursor to home, CLS, cursor
  176. // home. When we get here, we're ALWAYS at the top of the screen...
  177. // Hence our bat isn't displayed at the end of the screen.
  178. // This is before the actual CLS, so we CLS before displaying our files.
  179. // I tried a ^P2 before doing this .. but I'd rather have the picture up
  180. // right away I think.
  181. // Ok, yes, there's no filename being sent. :P
  182. render_image(images[r].lines, images[r].size);
  183. display << (needs_cls ? "\x1b[2J" : "") << fgoto
  184. << TRIGGER "S0" TRIGGER "CR" TRIGGER "P3";
  185. std::string display_output = display.str();
  186. ZF_LOGI("mangle(ANSI_CLS): %d file inserted %s", r,
  187. repr(display_output.c_str()));
  188. // Move the buffer so there's room for the display string.
  189. buffer.insert(pos, display_output);
  190. work.insert(pos, std::string(display_output.size(), ' '));
  191. return 1; // need_render = 1;
  192. } else {
  193. // if (random_activate((level + 1) / 2)) {
  194. if (random_activate(level * 5)) {
  195. int r;
  196. std::ostringstream display;
  197. // If the username is something, we have their info!
  198. bool have_userinfo = !username.empty();
  199. const char *phrasing[] = {
  200. TRIGGER "R1Haha" TRIGGER "P1ha" TRIGGER "P1ha", "Poof!",
  201. "Anyone there?", TRIGGER "R1Knock, " TRIGGER "P1Knock",
  202. /*
  203. This picks random color and position -- then
  204. homes cursor and changes to another color. (This can be seen.)
  205. */
  206. TRIGGER "G0101" TRIGGER "C07" TRIGGER "S0"
  207. "Segmentation fault" TRIGGER "P1"
  208. " (core dumped)" TRIGGER "P2",
  209. TRIGGER
  210. "G0101" TRIGGER "C07" TRIGGER "S0"
  211. "/usr/include/c++/7/bits/basic_string.h:1057: "
  212. "std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::reference "
  213. "std::__cxx11::basic_string<_CharT, _Traits, "
  214. "_Alloc>::operator[](std::__cxx11::basic_string<_CharT, _Traits, "
  215. "_Alloc>::size_type) [with _CharT = char; _Traits = "
  216. "std::char_traits<char>; _Alloc = std::allocator<char>; "
  217. "std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::reference = "
  218. "char&; std::__cxx11::basic_string<_CharT, _Traits, "
  219. "_Alloc>::size_type = long unsigned int]: Assertion '__pos <= "
  220. "size()' failed." TRIGGER "P1"
  221. "\r\nAborted (core dumped)" TRIGGER "P2" TRIGGER "S0",
  222. TRIGGER "G0101" TRIGGER "C07" TRIGGER "S0"
  223. "/usr/include/c++/7/debug/vector:417:\r\n"
  224. "Error: attempt to subscript container with out-of-bounds "
  225. "index 13, but\r\n"
  226. "container only holds 0 elements.\r\n"
  227. "\r\n"
  228. "Objects involved in the operation:\r\n"
  229. " sequence \"this\" @ 0x0x7fff43fa94a0 {\r\n"
  230. " type = "
  231. "std::__debug::vector<std::__cxx11::basic_string<char, "
  232. "std::char_traits<char>, std::allocator<char> >, "
  233. "std::allocator<std::__cxx11::basic_string<char, "
  234. "std::char_traits<char>, std::allocator<char> > > >;\r\n"
  235. " }\r\n"
  236. "Aborted (core dumped)" TRIGGER "P2" TRIGGER "S0"
  237. };
  238. // the Vector error could use some randomness in the element accessed,
  239. // as well as how many elements and the memory location.
  240. const int max_phrases = sizeof(phrasing) / sizeof(char *);
  241. const char *user_phrasing[] = {
  242. "We've got you now, NICK!",
  243. "Are you there, USER?",
  244. TRIGGER "R1Knock, " TRIGGER "P1Knock " TRIGGER "P1NICK",
  245. "I see you," TRIGGER "P1" TRIGGER "S1" TRIGGER "R1 NICK" TRIGGER
  246. "S0" TRIGGER "R0",
  247. "I see NICK hiding."
  248. };
  249. const int max_user_phrases = sizeof(user_phrasing) / sizeof(char *);
  250. static LastSeen last_phrasing(LastSeen::best_guess(max_phrases));
  251. int total_possible =
  252. have_userinfo ? max_phrases + max_user_phrases : max_phrases;
  253. ZF_LOGI("mangle(ANSI_CLS)");
  254. do {
  255. r = randint(total_possible);
  256. } while (last_phrasing.seen_before(r));
  257. std::string selected;
  258. if (r >= max_phrases) {
  259. selected = user_phrasing[r - max_phrases];
  260. std::string temp = username;
  261. a_or_an(temp);
  262. replace(selected, " a NICK", temp);
  263. replace(selected, "USER", fullname);
  264. replace(selected, "NICK", username);
  265. } else
  266. selected = phrasing[r];
  267. int color = randint(14) + 1;
  268. int x = randint(30) + 1;
  269. int y = randint(15) + 1;
  270. /*
  271. Don't have it pause there before moving the cursor.
  272. Move the cursor, get the color changed, THEN pause.
  273. Then act all crazy.
  274. NOTE: Make sure if you use any ^R Render effects, turn them off
  275. before trying to display the restore_color. :P ^R0 Also, make
  276. sure you re-home the cursor ^G0101 because that's where they are
  277. expecting the cursor to be! (At least it's how Mystic does it.)
  278. HOME, CLS, HOME, ... Not sure what others do there. We'll see.
  279. */
  280. if (strncmp(selected.c_str(), TRIGGER "G", 2) == 0) {
  281. display << TRIGGER "CS" TRIGGER "S3" TRIGGER "P1" << selected
  282. << TRIGGER "S0" TRIGGER "R0" TRIGGER "CR" TRIGGER "P1" TRIGGER
  283. "G0101";
  284. // This starts with a GOTO, so don't use our random position
  285. } else {
  286. display << TRIGGER "CS" TRIGGER "G" << std::setw(2)
  287. << std::setfill('0') << x << std::setw(2) << y
  288. << TRIGGER "S3" TRIGGER "C" << std::setw(2) << color
  289. << TRIGGER "P1" << selected
  290. << TRIGGER "S0" TRIGGER "R0" TRIGGER "CR" TRIGGER "P1" TRIGGER
  291. "G0101";
  292. };
  293. std::string display_output = display.str();
  294. // Added debug statement so we can identify what was sent... color,
  295. // number picked and what that is
  296. ZF_LOGD("mangle(ANSI_CLS): Inserted color=%02d r=%d phrase='%s'", color,
  297. r, phrasing[r]);
  298. ZF_LOGI("mangle(ANSI_CLS): %d %s", r, repr(display_output.c_str()));
  299. // Move the buffer so there's room for the display string.
  300. buffer.insert(pos, display_output);
  301. work.insert(pos, std::string(display_output.size(), ' '));
  302. return 1; // need_render = 1;
  303. }
  304. }
  305. }
  306. return 0;
  307. }
  308. int mangle(int fd, std::string &buffer) {
  309. // a simple default for now.
  310. // ZF_LOGV("mangle [%s]", logrepr(buffer.c_str()));
  311. ZF_LOGV_LR("mangle:", buffer);
  312. /*
  313. ZF_LOGV_MEM(buffer.data(), buffer.size(), "mangle(%d): %lu bytes", fd,
  314. buffer.size());
  315. */
  316. int need_render = 0;
  317. static std::string work;
  318. static size_t work_size = 0;
  319. work.assign(buffer);
  320. // This should allow us to monitor any memory allocations
  321. if (work.capacity() != work_size) {
  322. ZF_LOGD("work cap %lu -> %lu", work_size, work.capacity());
  323. work_size = work.capacity();
  324. }
  325. int level = harry_level();
  326. std::ostringstream new_buffer;
  327. std::smatch match;
  328. if (level) {
  329. // Strings are good, but Regex is better
  330. // Mystic BBS v1.12 A43 for Linux Node 1
  331. // Mystic BBS Version 1.12 A45
  332. static int bbs_match = 0;
  333. if (!bbs_match) {
  334. std::regex bbs_what(
  335. "(?:Mystic BBS Version [0-9.]+ A[0-9]+)|(?:Mystic BBS "
  336. "v[0-9.]+ A[0-9]+ for Linux Node [0-9]+)");
  337. // std::regex bbs_what("Mystic BBS Version [0-9.]+ A[0-9]+");
  338. // std::regex_constants::ECMAScript);
  339. // Mystic BBS v[0-9.]+ A[0-9]+ for Linux Node [0-9]+
  340. if (std::regex_search(buffer, match, bbs_what)) {
  341. // We have a match
  342. ZF_LOGD("bbs_seen");
  343. bbs_match = 1;
  344. std::string old_string =
  345. buffer.substr(match.position(0), match.length(0));
  346. // Build a new and better string
  347. std::string new_string;
  348. const char *bbs_systems[] = {
  349. "Haunted BBS", "Harry's BBS", "Scary BBS Software",
  350. "Screaming BBS", "Fright BBS", "Gravestone BBS",
  351. };
  352. const char *operating_systems[] = {
  353. "OS/360", "CP/M", "OS/9",
  354. "Xenix", "MS-DOS", "PC-DOS",
  355. "DR-DOS", "QNX", "Novell Netware",
  356. "AmigaOS", "Windows NT", "Windows CE",
  357. "AIX", "OS/2", "OS/400",
  358. "NeXTSTEP", "MINIX", "Solaris",
  359. "Plan 9", "FreeBSD", "Windows 95",
  360. "Palm OS", "Mac OS X", "Windows XP",
  361. "DESQview", "EMACS",
  362. // EMACS *should* be an OS!
  363. };
  364. int r = randint(sizeof(bbs_systems) / sizeof(char *));
  365. new_buffer << bbs_systems[r] << " v" << randint(10) << "."
  366. << randint(80);
  367. new_buffer << " for ";
  368. r = randint(sizeof(operating_systems) / sizeof(char *));
  369. new_buffer << operating_systems[r] << " Node " << randint(150 * level);
  370. new_string = new_buffer.str();
  371. // reset buffer
  372. new_buffer.str(std::string());
  373. new_buffer.clear();
  374. replace(buffer, old_string, new_string);
  375. replace(work, old_string, new_string);
  376. level = 0; // temp turn off the manglers! ;)
  377. }
  378. }
  379. static int author_match = 0;
  380. if (!author_match) {
  381. static std::regex author("Copyright \\(C\\) [0-9-]+ By James Coyle");
  382. if (std::regex_search(buffer, match, author)) {
  383. // We have a match
  384. ZF_LOGD("author seen");
  385. author_match = 1;
  386. std::string old_author =
  387. buffer.substr(match.position(0), match.length(0));
  388. // Build a new and better string
  389. const char *coder_names[] = {"Horrible Harry", "Ghost Writer",
  390. "Sands of Time", "Spector Software",
  391. "Creepy Coder"};
  392. if (randint(10) < 5)
  393. new_buffer << "Copyfright ";
  394. else
  395. new_buffer << "Copyright ";
  396. new_buffer << "(C) " << 1000 + randint(999) << "-" << randint(24000)
  397. << " By ";
  398. int r = randint(sizeof(coder_names) / sizeof(char *));
  399. new_buffer << coder_names[r];
  400. std::string new_author = new_buffer.str();
  401. replace(buffer, old_author, new_author);
  402. replace(work, old_author, new_author);
  403. level = 0;
  404. }
  405. }
  406. }
  407. const char *ANSI_CLS = "\x1b[2J";
  408. size_t pos = buffer.find(ANSI_CLS);
  409. if (pos != std::string::npos) {
  410. if (level)
  411. if (mangle_clrscr(buffer, work, pos)) {
  412. need_render = 1;
  413. }
  414. }
  415. static std::string text;
  416. static std::vector<int> text_offsets;
  417. size_t stri;
  418. text.clear();
  419. text_offsets.clear();
  420. // Don't use console, this for just spliting text and ansi into different
  421. // buffers.
  422. console_details chew = console;
  423. for (stri = 0; stri < buffer.size(); ++stri) {
  424. termchar tc = console_char(&chew, work[stri]);
  425. if (tc.in_ansi) {
  426. if (tc.ansi != START) {
  427. // Ok, this is something. What is it?
  428. // ZF_LOGV("ANSI type %d at %lu", tc.ansi, stri);
  429. switch (tc.ansi) {
  430. case CURSOR:
  431. case CLEAR:
  432. case OTHER:
  433. text.append(1, '.');
  434. text_offsets.push_back(-1);
  435. break;
  436. case START:
  437. case COLOR:
  438. // text.append(1, ' ');
  439. // text_offsets.push_back(-1);
  440. // color changes show as nothing in the text string.
  441. break;
  442. }
  443. }
  444. } else {
  445. // These should never get out of sync ...
  446. if (text.size() != text_offsets.size()) {
  447. ZF_LOGE("Error: text != text_offsets %lu != %lu", text.size(),
  448. text_offsets.size());
  449. }
  450. text.append(1, work[stri]);
  451. text_offsets.push_back(stri);
  452. }
  453. }
  454. // Control buffer debugging output.
  455. #ifndef NO_BUFFER_DEBUG
  456. // ZF_LOGV_LR("mangle:", buffer);
  457. ZF_LOGV_LR("Buffer:", buffer);
  458. ZF_LOGV_LR("Work:", work);
  459. ZF_LOGV_LR("Text:", text);
  460. // ZF_LOGV("Buffer: %s", logrepr(buffer.c_str()));
  461. // ZF_LOGV("Work: %s", logrepr(work.c_str()));
  462. // ZF_LOGV("Text: %s", logrepr(text.c_str()));
  463. // ZF_LOGV_MEM(buffer.data(), buffer.size(), "Buffer:");
  464. // ZF_LOGV_MEM(work.data(), work.size(), "Work:");
  465. // ZF_LOGV_MEM(text.data(), text.size(), "Text Buffer:");
  466. // Output vector contents
  467. std::ostringstream oss;
  468. int comma = 0;
  469. for (auto it = std::begin(text_offsets); it != std::end(text_offsets); ++it) {
  470. if (comma) {
  471. oss << ", ";
  472. };
  473. comma++;
  474. oss << *it;
  475. if (comma == 30) {
  476. std::string temp_output = oss.str();
  477. ZF_LOGV("Vector: %s", temp_output.c_str());
  478. // reset ostringstream
  479. oss.str(std::string());
  480. oss.clear();
  481. comma = 0;
  482. }
  483. }
  484. std::string vector_output = oss.str();
  485. ZF_LOGV("Vector: %s", vector_output.c_str());
  486. // reset oss (if we need it)
  487. oss.str(std::string());
  488. oss.clear();
  489. #endif
  490. // Begin the mangle process 2.0
  491. if (level) {
  492. ZF_LOGD("CharMan");
  493. CharMan cm(buffer, work, text, text_offsets);
  494. ZF_LOGD("CharMan %d, %d chars, render %d", cm.mangle_count, cm.mangle_chars,
  495. cm.need_render);
  496. if (cm.need_render)
  497. need_render = 1;
  498. };
  499. if (need_render) {
  500. render(fd, buffer);
  501. } else {
  502. write(fd, buffer.data(), buffer.size());
  503. console_receive(&console, buffer);
  504. }
  505. return need_render;
  506. }