Added external crash handler

This commit is contained in:
Sanel Zukan 2008-01-05 13:23:04 +00:00
parent d7db3f0d0f
commit 3326269e36
8 changed files with 952 additions and 0 deletions

View File

@ -16,6 +16,7 @@ Clean distclean : $(JCACHEFILE) $(HCACHEFILE) ;
# SubInclude goes after local rules
SubInclude TOP econtrol ;
SubInclude TOP ecalc ;
SubInclude TOP ecrasher ;
SubInclude TOP edewm ;
SubInclude TOP eiconman ;
SubInclude TOP evoke ;

286
ecrasher/CrashDialog.cpp Normal file
View File

@ -0,0 +1,286 @@
/*
* $Id$
*
* Ecrasher, a crash handler tool
* Part of Equinox Desktop Environment (EDE).
* Copyright (c) 2008 EDE Authors.
*
* This program is licensed under terms of the
* GNU General Public License version 2 or newer.
* See COPYING for details.
*/
#include "icons/core.xpm"
#include "CrashDialog.h"
#include <FL/Fl.h>
#include <FL/Fl_Pixmap.h>
#include <FL/Fl_File_Chooser.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <edelib/Nls.h>
#include <edelib/File.h>
#include <edelib/Directory.h>
#include <edelib/MessageBox.h>
#define DIALOG_W 380
#define DIALOG_H 130
#define DIALOG_W_EXPANDED 380
#define DIALOG_H_EXPANDED 340
int spawn_backtrace(const char* gdb_path, const char* program, const char* core, const char* output, const char* script) {
const char* gdb_script = "bt\nquit\n";
const int gdb_script_len = 8;
// file with gdb commands
int sfd = open(script, O_WRONLY | O_TRUNC | O_CREAT, 0770);
if(sfd == -1)
return -1;
write(sfd, gdb_script, gdb_script_len);
close(sfd);
// output file with gdb backtrace
int ofd = open(output, O_WRONLY | O_TRUNC | O_CREAT, 0770);
if(ofd == -1)
return -1;
pid_t pid = fork();
if(pid == -1) {
close(ofd);
return -1;
} else if(pid == 0) {
dup2(ofd, 1);
close(ofd);
char* argv[8];
argv[0] = (char*)gdb_path;
argv[1] = "--quiet";
argv[2] = "--batch";
argv[3] = "-x";
argv[4] = (char*)script;
argv[5] = (char*)program;
argv[6] = (char*)core;
argv[7] = 0;
execvp(argv[0], argv);
return -1;
} else {
int status;
if(waitpid(pid, &status, 0) != pid)
return -1;
}
return 0;
}
edelib::String get_uname(void) {
struct utsname ut;
uname(&ut);
edelib::String ret;
ret.printf("%s %s %s %s %s", ut.sysname, ut.nodename, ut.release, ut.version, ut.machine);
return ret;
}
void show_details_cb(Fl_Widget*, void* cd) {
CrashDialog* c = (CrashDialog*)cd;
c->show_details();
}
void close_cb(Fl_Widget*, void* cd) {
CrashDialog* c = (CrashDialog*)cd;
c->hide();
}
void save_cb(Fl_Widget*, void* cd) {
CrashDialog* c = (CrashDialog*)cd;
c->save();
}
CrashDialog::CrashDialog() : Fl_Window(DIALOG_W, DIALOG_H, _("EDE crash handler")),
appname(NULL), apppath(NULL), bugaddress(NULL), pid(NULL), signal_num(NULL) {
details_shown = false;
begin();
pix = new Fl_Pixmap(core_xpm);
icon_box = new Fl_Box(10, 10, 70, 75);
icon_box->image(pix);
txt_box = new Fl_Box(85, 10, 285, 75);
txt_box->align(FL_ALIGN_WRAP | FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
close = new Fl_Button(280, 95, 90, 25, _("&Close"));
close->callback(close_cb, this);
details = new Fl_Button(10, 95, 265, 25, _("@> Show details"));
details->box(FL_FLAT_BOX);
details->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT);
details->callback(show_details_cb, this);
// widgets for expanded dialog
trace_log = new Fl_Text_Display(10, 130, 360, 165);
trace_buff = new Fl_Text_Buffer();
trace_log->buffer(trace_buff);
trace_log->hide();
save_as = new Fl_Button(280, 305, 90, 25, _("&Save As..."));
save_as->callback(save_cb, this);
save_as->hide();
end();
}
CrashDialog::~CrashDialog() {
// looks like fltk does not clean image() assigned data
delete pix;
}
void CrashDialog::show_details(void) {
if(trace_log->visible()) {
trace_log->hide();
save_as->hide();
details->label(_("@> Show details"));
size(DIALOG_W, DIALOG_H);
} else {
trace_log->show();
save_as->show();
details->label(_("@< Hide details"));
size(DIALOG_W_EXPANDED, DIALOG_H_EXPANDED);
if(!details_shown) {
trace_buff->remove(0, trace_buff->length());
edelib::String address = _("\nPlease report this at: ");
if(bugaddress)
address += bugaddress;
else
address += "bugs@equinox-project.org";
trace_buff->append(address.c_str());
trace_buff->append("\n\n");
trace_buff->append("---------- short summary ----------\n");
trace_buff->append("\nEDE version: 2.0");
trace_buff->append("\nSystem info: ");
trace_buff->append(get_uname().c_str());
trace_buff->append("\nProgram name: ");
if(appname)
trace_buff->append(appname);
else
trace_buff->append("(unknown)");
trace_buff->append("\nExecutable path: ");
if(apppath)
trace_buff->append(apppath);
else
trace_buff->append("(unknown)");
trace_buff->append("\nRunning PID: ");
if(pid)
trace_buff->append(pid);
else
trace_buff->append("(unknown)");
trace_buff->append("\nSignal received: ");
if(signal_num)
trace_buff->append(signal_num);
else
trace_buff->append("(unknown)");
// try backtrace via gdb
trace_buff->append("\n\n---------- backtrace ----------\n");
const char* core_file = "core";
if(!edelib::file_exists(core_file)) {
trace_buff->append("\nUnable to find 'core' file. Backtrace will not be done.");
details_shown = false;
return;
}
edelib::String gdb_path = edelib::file_path("gdb");
if(gdb_path.empty()) {
trace_buff->append("\nUnable to find gdb. Is it installed ?");
// set to false so next 'Show Details' click can try again with the debugger
details_shown = false;
return;
}
// check if we can write in /tmp; if not, try with $HOME
edelib::String dir = "/tmp";
if(!edelib::dir_writeable(dir.c_str())) {
dir = edelib::dir_home();
if(!edelib::dir_writeable(dir.c_str())) {
trace_buff->append("\nDon't have permissions to write either to /tmp or $HOME");
details_shown = false;
return;
}
}
edelib::String gdb_output, gdb_script;
gdb_output = gdb_script = dir;
// TODO: these files should be unique per session
gdb_output += "/.gdb_output";
gdb_script += "/.gdb_script";
if(spawn_backtrace(gdb_path.c_str(), cmd.c_str(), core_file, gdb_output.c_str(), gdb_script.c_str()) == -1) {
trace_buff->append("\nUnable to properly execute gdb");
details_shown = false;
return;
}
if(!edelib::file_exists(gdb_output.c_str())) {
trace_buff->append("\nStrange, can't find gdb output that I was just wrote to");
details_shown = false;
return;
}
trace_buff->appendfile(gdb_output.c_str());
edelib::file_remove(gdb_output.c_str());
edelib::file_remove(gdb_script.c_str());
edelib::file_remove(core_file);
details_shown = true;
}
}
}
void CrashDialog::save(void) {
const char* p = fl_file_chooser(_("Save details to..."), "Text Files (*.txt)\tAll Files(*)", "dump.txt");
if(!p)
return;
// so we can have EOL in file
trace_buff->append("\n");
if(trace_buff->savefile(p) != 0)
edelib::alert(_("Unable to save to %s. Please check permissions to write in this directory or file"), p);
}
void CrashDialog::run(void) {
edelib::String l;
if(appname || apppath) {
const char* p = (appname ? appname : apppath);
l.printf(_("Program '%s' just crashed !"), p);
} else
l += _("Program just crashed !");
l += _("\n\nYou can inspect details about this crash by clicking on 'Show details' below");
txt_box->copy_label(l.c_str());
if(!shown())
show();
while(shown())
Fl::wait();
}

62
ecrasher/CrashDialog.h Normal file
View File

@ -0,0 +1,62 @@
/*
* $Id$
*
* Ecrasher, a crash handler tool
* Part of Equinox Desktop Environment (EDE).
* Copyright (c) 2008 EDE Authors.
*
* This program is licensed under terms of the
* GNU General Public License version 2 or newer.
* See COPYING for details.
*/
#ifndef __CRASHDIALOG_H__
#define __CRASHDIALOG_H__
#include <FL/Fl_Window.h>
#include <FL/Fl_Box.h>
#include <FL/Fl_Button.h>
#include <FL/Fl_Pixmap.h>
#include <FL/Fl_Text_Display.h>
#include <FL/Fl_Text_Buffer.h>
#include <edelib/String.h>
class CrashDialog : public Fl_Window {
private:
const char* appname;
const char* apppath;
const char* bugaddress;
const char* pid;
const char* signal_num;
Fl_Pixmap* pix;
Fl_Box* txt_box;
Fl_Box* icon_box;
Fl_Button* close;
Fl_Button* details;
Fl_Text_Display* trace_log;
Fl_Text_Buffer* trace_buff;
Fl_Button* save_as;
edelib::String cmd;
bool details_shown;
public:
CrashDialog();
~CrashDialog();
void show_details(void);
void set_appname(const char* a) { appname = a; }
void set_apppath(const char* p) { apppath = p; }
void set_bugaddress(const char* a) { bugaddress = a; }
void set_pid(const char* p) { pid = p; }
void set_signal(const char* s) { signal_num = s; }
void save(void);
void run(void);
};
#endif

15
ecrasher/Jamfile Normal file
View File

@ -0,0 +1,15 @@
#
# $Id$
#
# Part of Equinox Desktop Environment (EDE).
# Copyright (c) 2008 EDE Authors.
#
# This program is licensed under terms of the
# GNU General Public License version 2 or newer.
# See COPYING for details.
SubDir TOP ecrasher ;
SOURCE = ecrasher.cpp CrashDialog.cpp ;
EdeProgram ecrasher : $(SOURCE) ;

109
ecrasher/ecrasher.cpp Normal file
View File

@ -0,0 +1,109 @@
/*
* $Id$
*
* Ecrasher, a crash handler tool
* Part of Equinox Desktop Environment (EDE).
* Copyright (c) 2008 EDE Authors.
*
* This program is licensed under terms of the
* GNU General Public License version 2 or newer.
* See COPYING for details.
*/
#include <stdio.h>
#include <string.h>
#include "CrashDialog.h"
#define CHECK_ARGV(argv, pshort, plong) ((strcmp(argv, pshort) == 0) || (strcmp(argv, plong) == 0))
const char* next_param(int curr, char** argv, int argc) {
int j = curr + 1;
if(j >= argc)
return NULL;
if(argv[j][0] == '-')
return NULL;
return argv[j];
}
void help(void) {
puts("Usage: ecrasher [OPTIONS]");
puts("EDE crash handler\n");
puts("Options:");
puts(" -h, --help this help");
puts(" -b, --bugaddress [ADDRESS] bug address to use");
puts(" -p, --pid [PID] the PID of the program");
puts(" -a, --appname [NAME] name of the program");
puts(" -e, --apppath [PATH] path to the executable");
puts(" -s, --signal [SIGNAL] the signal number that was caught");
}
int main(int argc, char** argv) {
if(argc <= 1) {
help();
return 0;
}
const char* a;
const char* bugaddress = NULL;
const char* appname = NULL;
const char* apppath = NULL;
const char* pid = 0;
const char* signal_num = 0;
for(int i = 1; i < argc; i++) {
a = argv[i];
if(CHECK_ARGV(a, "-h", "--help")) {
help();
return 0;
} else if(CHECK_ARGV(a, "-b", "--bugaddress")) {
bugaddress = next_param(i, argv, argc);
if(!bugaddress) {
puts("Missing bug address parameter");
return 1;
}
i++;
} else if(CHECK_ARGV(a, "-p", "--pid")) {
pid = next_param(i, argv, argc);
if(!pid) {
puts("Missing pid parameter");
return 1;
}
i++;
} else if(CHECK_ARGV(a, "-a", "--appname")) {
appname = next_param(i, argv, argc);
if(!appname) {
puts("Missing application name");
return 1;
}
i++;
} else if(CHECK_ARGV(a, "-e", "--apppath")) {
apppath = next_param(i, argv, argc);
if(!apppath) {
puts("Missing application path");
return 1;
}
i++;
} else if(CHECK_ARGV(a, "-s", "--signal")) {
signal_num = next_param(i, argv, argc);
if(!signal_num) {
puts("Missing signal number");
return 1;
}
i++;
} else {
printf("Unknown '%s' parameter. Run ecrasher -h for options\n", a);
return 1;
}
}
CrashDialog cd;
cd.set_appname(appname);
cd.set_apppath(apppath);
cd.set_bugaddress(bugaddress);
cd.set_pid(pid);
cd.set_signal(signal_num);
cd.run();
return 0;
}

28
ecrasher/fl/crash.fl Normal file
View File

@ -0,0 +1,28 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0108
header_name {.h}
code_name {.cxx}
Function {} {open
} {
Fl_Window {} {open
xywh {354 160 380 130} type Double visible
} {
Fl_Box {} {
image {../icons/core.xpm} xywh {10 10 70 75} labelsize 14
}
Fl_Box {} {
label {Program foo just crashed !!!
You can inspect details about this crash by clicking on 'Show details' below} selected
xywh {85 10 285 75} align 148
}
Fl_Button {} {
label {&Close}
xywh {280 95 90 25}
}
Fl_Button {} {
label {@> Show details}
xywh {10 95 265 25} box FLAT_BOX align 20
}
}
}

View File

@ -0,0 +1,35 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0108
header_name {.h}
code_name {.cxx}
Function {} {open selected
} {
Fl_Window {} {open
xywh {354 160 380 340} type Double visible
} {
Fl_Box {} {
image {../icons/core.xpm} xywh {10 10 70 75} labelsize 14
}
Fl_Box {} {
label {Program foo just crashed !!!
You can inspect details about this crash by clicking on 'Show details' below}
xywh {85 10 285 75} align 148
}
Fl_Button {} {
label {&Close}
xywh {280 95 90 25}
}
Fl_Button {} {
label {@< Hide details}
xywh {10 95 265 25} box FLAT_BOX align 20
}
Fl_Text_Display {} {
xywh {10 130 360 165} labelsize 14 textsize 14
}
Fl_Button {} {
label {&Save As...}
xywh {280 305 90 25}
}
}
}

416
ecrasher/icons/core.xpm Normal file
View File

@ -0,0 +1,416 @@
/* XPM */
static char * core_xpm[] = {
"48 48 365 2",
" c None",
". c #FAE601",
"+ c #FCF001",
"@ c #FEF800",
"# c #F9E202",
"$ c #F2BE04",
"% c #F2BD04",
"& c #F5CC03",
"* c #FBE901",
"= c #FBEC01",
"- c #EB9A06",
"; c #EEAA05",
"> c #FEFC00",
", c #F7D506",
"' c #E78808",
") c #E88D06",
"! c #D1B46D",
"~ c #C7AF75",
"{ c #C2994A",
"] c #F1BB04",
"^ c #F5CF03",
"/ c #FCEE01",
"( c #EDD426",
"_ c #DCBC76",
": c #E3C06E",
"< c #CCAB4F",
"[ c #E7C16F",
"} c #ECC36B",
"| c #CCB34C",
"1 c #FCFB04",
"2 c #D1C652",
"3 c #D0B76B",
"4 c #DDBA6C",
"5 c #DAB96E",
"6 c #CFBB40",
"7 c #A7A7A7",
"8 c #A8A8A8",
"9 c #A9A9A9",
"0 c #A4A4A4",
"a c #9F9F9F",
"b c #999999",
"c c #919191",
"d c #A5B4BE",
"e c #E7E7E1",
"f c #C3C2C7",
"g c #B3AB9B",
"h c #B5A679",
"i c #C5AF6A",
"j c #D6B86A",
"k c #CDB16B",
"l c #C9B05B",
"m c #B7A27C",
"n c #EBDC19",
"o c #AFAFAF",
"p c #B8B8B8",
"q c #BBBBBB",
"r c #BCBCBC",
"s c #BABABA",
"t c #B4B4B4",
"u c #888888",
"v c #7B7B7B",
"w c #102032",
"x c #808285",
"y c #FBF5F2",
"z c #D9D0BF",
"A c #C6AD71",
"B c #DFBB69",
"C c #D9B86B",
"D c #C0AC7C",
"E c #BBA785",
"F c #AAAAAA",
"G c #ADADAD",
"H c #D6D6D6",
"I c #EBEBEB",
"J c #FCFCFC",
"K c #FEFEFE",
"L c #FFFFFF",
"M c #FBFBFB",
"N c #F5F5F5",
"O c #E7E7E7",
"P c #D2D2D2",
"Q c #868686",
"R c #636363",
"S c #616161",
"T c #6E6E6E",
"U c #3E3E3D",
"V c #3B3A3A",
"W c #161311",
"X c #65605D",
"Y c #C3BCB7",
"Z c #D8CFB0",
"` c #D1B873",
" . c #DFBD6A",
".. c #C8B37E",
"+. c #AAAEBF",
"@. c #FFFD00",
"#. c #B1B1B1",
"$. c #CACACA",
"%. c #E9E9E9",
"&. c #F0F0F0",
"*. c #E5E5E5",
"=. c #E3E3E3",
"-. c #E1E1E1",
";. c #DFDFDF",
">. c #898989",
",. c #3C3C3C",
"'. c #393939",
"). c #262626",
"!. c #1D1D1E",
"~. c #0F0F0F",
"{. c #5A5857",
"]. c #5A5955",
"^. c #4D4D45",
"/. c #918E7A",
"(. c #D3CBA7",
"_. c #E5DCB3",
":. c #E0DECD",
"<. c #AEAEAE",
"[. c #D3D3D3",
"}. c #FAFAFA",
"|. c #FDFDFD",
"1. c #EFEFEF",
"2. c #EAEAEA",
"3. c #DDDDDD",
"4. c #D7D7D7",
"5. c #D0D0D0",
"6. c #C9C9C9",
"7. c #C1C1C1",
"8. c #BDBDBD",
"9. c #7D7D7D",
"0. c #333333",
"a. c #131313",
"b. c #4F4F4F",
"c. c #484848",
"d. c #5F5F60",
"e. c #393C3A",
"f. c #080C09",
"g. c #1F1F1B",
"h. c #817F74",
"i. c #D8D6C6",
"j. c #F2F2E8",
"k. c #DFE7F0",
"l. c #F9F9F9",
"m. c #F3F3F3",
"n. c #EDEDED",
"o. c #E8E8E8",
"p. c #E4E4E4",
"q. c #DEDEDE",
"r. c #CCCCCC",
"s. c #C3C3C3",
"t. c #B9B9B9",
"u. c #ABABAB",
"v. c #A1A1A1",
"w. c #151515",
"x. c #4B4B4B",
"y. c #6B6B6B",
"z. c #3F3F3F",
"A. c #1B1B1B",
"B. c #121211",
"C. c #050604",
"D. c #000000",
"E. c #0C0707",
"F. c #433F37",
"G. c #7E7F79",
"H. c #F7F7F7",
"I. c #ECECEC",
"J. c #E2E2E2",
"K. c #DCDCDC",
"L. c #B0B0B0",
"M. c #A5A5A5",
"N. c #979797",
"O. c #969696",
"P. c #434343",
"Q. c #191919",
"R. c #010101",
"S. c #060001",
"T. c #0C0203",
"U. c #050002",
"V. c #0B0D14",
"W. c #EEEEEE",
"X. c #DADADA",
"Y. c #C6C6C6",
"Z. c #BEBEBE",
"`. c #B6B6B6",
" + c #9C9C9C",
".+ c #909090",
"++ c #848484",
"@+ c #6C6C6C",
"#+ c #1A1A1A",
"$+ c #060606",
"%+ c #020101",
"&+ c #010403",
"*+ c #020405",
"=+ c #010202",
"-+ c #D8D8D8",
";+ c #CFCFCF",
">+ c #C2C2C2",
",+ c #A2A2A2",
"'+ c #878787",
")+ c #787878",
"!+ c #656565",
"~+ c #252525",
"{+ c #080C0D",
"]+ c #F4F4F4",
"^+ c #D9D9D9",
"/+ c #D4D4D4",
"(+ c #CECECE",
"_+ c #C5C5C5",
":+ c #BFBFBF",
"<+ c #B2B2B2",
"[+ c #A0A0A0",
"}+ c #989898",
"|+ c #8E8E8E",
"1+ c #737373",
"2+ c #6D6D6D",
"3+ c #585858",
"4+ c #202020",
"5+ c #070707",
"6+ c #4B4C4C",
"7+ c #767676",
"8+ c #D1D1D1",
"9+ c #A3A3A3",
"0+ c #9B9B9B",
"a+ c #949494",
"b+ c #8C8C8C",
"c+ c #838383",
"d+ c #797979",
"e+ c #6F6F6F",
"f+ c #676767",
"g+ c #4A4A4A",
"h+ c #030303",
"i+ c #181818",
"j+ c #040404",
"k+ c #565656",
"l+ c #D5D5D5",
"m+ c #C7C7C7",
"n+ c #C4C4C4",
"o+ c #ACACAC",
"p+ c #9E9E9E",
"q+ c #7E7E7E",
"r+ c #666666",
"s+ c #606060",
"t+ c #575757",
"u+ c #353535",
"v+ c #383738",
"w+ c #777677",
"x+ c #494949",
"y+ c #CBCBCB",
"z+ c #B5B5B5",
"A+ c #9A9A9A",
"B+ c #939393",
"C+ c #8B8B8B",
"D+ c #7C7C7C",
"E+ c #5E5E5E",
"F+ c #595959",
"G+ c #424242",
"H+ c #171717",
"I+ c #0C0C0C",
"J+ c #5D5D5D",
"K+ c #2F2F2F",
"L+ c #8F8F8F",
"M+ c #8A8A8A",
"N+ c #858585",
"O+ c #7F7F7F",
"P+ c #757575",
"Q+ c #686868",
"R+ c #5B5B5B",
"S+ c #4D4D4D",
"T+ c #6F6D6E",
"U+ c #3B3B3B",
"V+ c #9D9D9D",
"W+ c #959595",
"X+ c #8D8D8D",
"Y+ c #828282",
"Z+ c #6A6A6A",
"`+ c #646464",
" @ c #555555",
".@ c #2E2E2E",
"+@ c #0B0B0B",
"@@ c #020202",
"#@ c #222222",
"$@ c #1D1D1D",
"%@ c #080808",
"&@ c #808080",
"*@ c #7A7A7A",
"=@ c #707070",
"-@ c #5F5F5F",
";@ c #545454",
">@ c #525252",
",@ c #2B2B2B",
"'@ c #0D0D0D",
")@ c #050505",
"!@ c #111111",
"~@ c #5A5A5A",
"{@ c #929292",
"]@ c #717171",
"^@ c #515151",
"/@ c #3D3D3D",
"(@ c #272727",
"_@ c #323232",
":@ c #6D6C6C",
"<@ c #1C1C1C",
"[@ c #535353",
"}@ c #414141",
"|@ c #0A0A0A",
"1@ c #646363",
"2@ c #161616",
"3@ c #696969",
"4@ c #313131",
"5@ c #1F1F1F",
"6@ c #0E0E0E",
"7@ c #090909",
"8@ c #121212",
"9@ c #444444",
"0@ c #464646",
"a@ c #282828",
"b@ c #5C5C5C",
"c@ c #141414",
"d@ c #212121",
"e@ c #101010",
"f@ c #616061",
"g@ c #626262",
"h@ c #232323",
"i@ c #373737",
"j@ c #5B5959",
"k@ c #1E1E1E",
"l@ c #2C2C2C",
"m@ c #505050",
"n@ c #494646",
"o@ c #303030",
"p@ c #626162",
"q@ c #292929",
"r@ c #2A2A2A",
"s@ c #616060",
"t@ c #2D2D2D",
"u@ c #575656",
"v@ c #363636",
"w@ c #3E3E3E",
"x@ c #5F5E5E",
"y@ c #3A3A3A",
"z@ c #242424",
"A@ c #343434",
"B@ c #383838",
"C@ c #474747",
"D@ c #4F4D4D",
"E@ c #737272",
"F@ c #404040",
"G@ c #5B5A5A",
"H@ c #696869",
"I@ c #4E4E4E",
"J@ c #3D3C3C",
"K@ c #5D5B5C",
"L@ c #6A696A",
"M@ c #454545",
"N@ c #5B595A",
"O@ c #6A6969",
"P@ c #515050",
"Q@ c #595859",
"R@ c #656464",
"S@ c #413F3F",
"T@ c #747373",
"U@ c #5F5E5F",
"V@ c #605F5F",
"W@ c #595858",
"X@ c #545252",
" . + ",
" @ # $ % & * ",
" = - ; $ ",
" > , ' ) ",
" > ! ~ { ] ^ ",
" / ( _ : < . ",
" [ } | ",
" 1 2 3 4 5 6 ",
" 7 8 9 0 a b c d e f g h i j k l m n ",
" 8 o p q r s t 8 b u v w x y z A B C D E ",
" F G s H I J K L M N O P t Q R S T U V W X Y Z ` ...+. @. ",
" 7 #.$.%.L L L M N &.%.*.=.-.;.$.>.,.'.).!.~.{.].^./.(._.:. ",
" <.[.}.L L |.N 1.2.=.3.4.5.6.7.8.7.s 9.0.a.b.c.d.e.f.g.h.i.j.k. ",
" #.I L L l.m.n.I o.p.q.4.[.r.s.t.u.v.G u w.x.y.z.A.B.C.D.E.F.G. ",
" #.m.L H.1.I.I.o.*.J.K.H 5.6.7.t.L.M.N.O.u P.S P.Q.R.D.S.T.U.D.V. ",
" 8 W.L &.%.o.O *.J.3.X.[.r.Y.Z.`.G 0 +.+Q ++@+0.#+$+D.R.%+&+*+=+ ",
" c q.L I.-.=.-.;.3.-+[.;+$.>+r t u.,+b c '+9.)+!+~+R.D.R.R.D.D.{+ ",
" )+L.]+o.^+X.^+H /+P (+6._+:+t.<+8 [+}+|+++v 1+2+3+4+D.D.D.D.D.5+6+ ",
" 7+8+%.4.8+8+5.(+$.Y.s.Z.p <+u.9+0+a+b+c+d+e+f+R g+w.D.D.D.h+i+j+k+ ",
" y.>.l+H 6.m+m+n+7.Z.q `.L.o+M.p+O..+u q+7+e+r+s+t+u+5+D.D.D.D.D.D.v+w+ ",
" x+a+y+_+q r q p z+<+<.9 0 a A+B+C+c+D+1+2+!+E+F+G+H+R.D.D.D.D.D.D.I+r+ ",
" J+K+L+Z.z+<.o G F 7 M.[+ +N.c M+N+O+P+e+Q+S R+F+S+~+h+D.D.D.D.D.D.D.D.c.T+ ",
" U+i+2+t 9 a [+V+A+}+W+c X+>.Y+v )+1+Z+`+J+t+ @x..@+@@@h+@@@@D.D.D.D.D.#@P+ ",
" $@%@U+O.0 B+X+|+b+M+'+c+&@*@P+=@@+`+-@F+;@>@c.,@'@j+j+j+j+j+@@D.D.D.D.)@t+ ",
"@++@D.!@~@{@.+&@O+O+v d+7+]@@+Q+!+s+3+ @^@g+/@(@'@)@$+$+j+j+$+$+h+R.D.D.D._@:@ ",
"J+@@D.D.<@~@D+D+7+1+=@T y.r+S -@R+3+[@x.}@K+#+|@)@$+$+$+)@$+5+%@%@h+D.D.D.$@1@ ",
"3+@@D.D.D.2@G+s+3@Q+r+`+S J+F+[@S+g+}@4@5@6@)@$+)@5+5+5+5+%@7@|@+@5+@@D.D.8@E+ ",
";@@@D.D.D.D.|@~+U+9@c.g+g+0@P.U+4@a@$@~.5+$+5+5+5+%@%@7@7@|@+@I+'@'@$+D.D.'@b@ ",
"b.@@D.D.D.D.R.@@+@c@$@d@#@5@<@H+!@7@$+5+5+5+%@|@+@+@7@+@I+'@6@6@e@a.6@h+D.I+~@ ",
"-@)@D.D.D.D.h+h+@@h+j+)@j+$+$+5+$+$+$+5+5+5+7@+@I+I+I+'@~.e@8@a.a.2@2@5+D.e@E+ ",
"-@+@D.D.D.D.h+@@h+j+j+)@)@$+$+5+%@%@%@%@7@|@|@+@'@'@6@~.!@!@a.w.w.2@<@7@D.a.f@ ",
"g@!@D.D.D.D.h+j+$+$+$+$+$+5+5+%@7@7@7@|@+@I+I+'@~.~.e@!@a.c@2@i+Q.#+h@I+D.#@!+ ",
" 5@@@D.D.D.$+$+$+$+5+5+5+%@7@7@|@|@|@+@I+'@e@8@8@c@2@2@H+#+A.<@$@4+a@~.D.i@j@ ",
" P.h+D.D.D.5+%@5+%@|@|@+@|@I+I+'@6@~.~.~.e@8@8@c@H+i+Q.#+<@k@4+d@).l@a.@@m@n@ ",
" Q+I+D.D.D.$+%@7@|@|@+@I+I+'@'@~.e@e@e@e@8@c@2@H+Q.A.$@k@4+h@~+).l@o@c@6@p@ ",
" ++q@@@D.D.%@|@+@I+I++@I+6@e@!@!@8@c@c@c@w.i+A.A.$@5@#@h@~+a@r@l@u+4@a._@s@ ",
" k+5+D.D.%@I+~.6@6@6@~.8@8@a.c@w.H+i+H+Q.<@5@5@d@h@).a@l@t@o@0.,.t@6@u@ ",
" =@).D.D.)@+@8@!@!@a.c@c@w.2@H+i+#+A.<@k@4+#@h@).q@,@.@_@0.v@z.w@<@4@x@ ",
" 3+6@D.R.$+8@2@w.2@H+i+Q.#+#+A.$@5@d@h@~+).a@,@.@o@0.v@y@P.x+K+2@x@ ",
" 9@j+D.@@6@Q.A.Q.Q.A.k@5@k@4+#@h@z@(@r@l@K+_@A@v@B@U+C@S+U+w.x.D@ ",
" E@i@R.D.)@8@k@#@d@5@d@h@z@~+(@q@a@l@K+4@A@B@'.,.P.x.b./@Q.F@G@ ",
" H@4@D.D.$+c@h@r@r@q@(@(@q@,@.@K+_@A@v@y@w@P.g+^@I@B@<@J@K@ ",
" L@0.)@D.)@8@#@o@0.0._@_@_@A@v@i@y@F@M@g+m@^@M@l@k@P.N@ ",
" O@z.6@D.@@|@A.r@i@w@/@w@F@}@P.0@x+x.x.P..@4+).P@Q@ ",
" F+(@+@j+j++@c@#@t@'.}@9@0@G+'..@#@H+z@^@R@S@ ",
" R@k+0.|@5+j+)@@@R.D.D.R.R.j+c@u+F+T@ ",
" U@s@;@,.z@|@'@)@5+i+d@,.E@ ",
" E+V@W@R+W@X@ "};