[input_dispatcher] fix handling of unicode input

Fixes #791
This commit is contained in:
Timothy Stack 2020-11-09 22:18:17 -08:00
parent db8a3c4d38
commit f192cb7c3e
15 changed files with 345 additions and 131 deletions

3
NEWS
View File

@ -20,6 +20,9 @@ lnav v0.9.1:
* When copying log lines, the file name and time offset will be included
in the copy if they are enabled.
Fixes:
* Unicode text can now be entered in prompts.
lnav v0.9.0:
Features:
* Added support for themes and included a few as well: default, eldar,

View File

@ -38,6 +38,7 @@
#include <vector>
#include "base/lnav_log.hh"
#include "base/string_util.hh"
#include "base/intern_string.hh"
/**
@ -464,6 +465,13 @@ public:
return *this;
};
attr_line_t &erase_utf8_chars(size_t start) {
auto byte_index = utf8_char_to_byte_index(this->al_string, start);
this->erase(byte_index);
return *this;
};
attr_line_t &right_justify(unsigned long width) {
long padding = width - this->length();
if (padding > 0) {

View File

@ -33,6 +33,8 @@
#include <string.h>
#include <string>
#include "ww898/cp_utf8.hpp"
void scrub_to_utf8(char *buffer, size_t length);
inline bool is_line_ending(char ch) {
@ -116,4 +118,20 @@ inline std::string toupper(const std::string &str)
return toupper(str.c_str());
}
inline ssize_t utf8_char_to_byte_index(const std::string &str, ssize_t ch_index)
{
ssize_t retval = 0;
while (ch_index > 0) {
auto ch_len = ww898::utf::utf8::char_size([&str, retval]() {
return str[retval];
});
retval += ch_len;
ch_index -= 1;
}
return retval;
}
#endif

View File

@ -50,9 +50,12 @@
#endif
#include "base/lnav_log.hh"
#include "ww898/cp_utf8.hpp"
#include "input_dispatcher.hh"
#include "lnav_util.hh"
using namespace ww898;
template<typename A>
static void to_key_seq(A &dst, const char *src)
{
@ -90,13 +93,9 @@ void input_dispatcher::new_input(const struct timeval &current_time, int ch)
to_key_seq(keyseq, this->id_escape_buffer);
switch (this->id_escape_matcher(keyseq.data())) {
case escape_match_t::NONE: {
if (this->id_escape_expected_size == -1) {
for (int lpc = 0; this->id_escape_buffer[lpc]; lpc++) {
handled = this->id_key_handler(
this->id_escape_buffer[lpc]);
}
} else {
handled = false;
for (int lpc = 0; this->id_escape_buffer[lpc]; lpc++) {
handled = this->id_key_handler(
this->id_escape_buffer[lpc]);
}
this->id_escape_index = 0;
break;
@ -113,15 +112,15 @@ void input_dispatcher::new_input(const struct timeval &current_time, int ch)
this->id_escape_index == this->id_escape_expected_size) {
this->id_escape_index = 0;
}
} else if ((ch & 0xf8) == 0xf0) {
this->reset_escape_buffer(ch, current_time, 4);
} else if ((ch & 0xf0) == 0xe0) {
this->reset_escape_buffer(ch, current_time, 3);
} else if ((ch & 0xe0) == 0xc0) {
this->reset_escape_buffer(ch, current_time, 2);
} else {
snprintf(keyseq.data(), keyseq.size(), "x%02x", ch & 0xff);
handled = this->id_key_handler(ch);
auto seq_size = utf::utf8::char_size([ch]() { return ch; });
if (seq_size == 1) {
snprintf(keyseq.data(), keyseq.size(), "x%02x", ch & 0xff);
handled = this->id_key_handler(ch);
} else {
this->reset_escape_buffer(ch, current_time, seq_size);
}
}
break;
}

View File

@ -1471,16 +1471,19 @@ static void looper()
auto encoded_name = (char *) alloca(enc_len);
log_info("unbound keyseq: %s", keyseq);
json_ptr::encode(encoded_name, enc_len, lnav_config.lc_ui_keymap.c_str());
json_ptr::encode(encoded_name, enc_len,
lnav_config.lc_ui_keymap.c_str());
// XXX we should have a hotkey for opening a prompt that is
// pre-filled with a suggestion that the user can complete.
// This quick-fix key could be used for other stuff as well
lnav_data.ld_rl_view->set_value(fmt::format(
ANSI_CSI ANSI_COLOR_PARAM(COLOR_YELLOW) ";" ANSI_BOLD_PARAM ANSI_CHAR_ATTR
ANSI_CSI ANSI_COLOR_PARAM(COLOR_YELLOW)
";" ANSI_BOLD_PARAM ANSI_CHAR_ATTR
"Unrecognized key"
ANSI_NORM
", bind to a command using \u2014 "
ANSI_BOLD(":config") " /ui/keymap-defs/{}/{}/command <cmd>",
ANSI_BOLD(":config")
" /ui/keymap-defs/{}/{}/command <cmd>",
encoded_name, keyseq));
alerter::singleton().chime();
};

View File

@ -319,18 +319,12 @@ void view_curses::mvwattrline(WINDOW *window,
break;
default: {
int offset = 0;
auto offset = 1 - (int) ww898::utf::utf8::char_size([ch]() {
return ch;
});
expanded_line[exp_index] = line[lpc];
exp_index += 1;
if ((ch & 0xf8) == 0xf0) {
offset = -3;
} else if ((ch & 0xf0) == 0xe0) {
offset = -2;
} else if ((ch & 0xe0) == 0xc0) {
offset = -1;
}
if (offset) {
if (char_index < lr_chars.lr_start) {
lr_bytes.lr_start += abs(offset);

View File

@ -31,7 +31,6 @@
#include "config.h"
#include <unistd.h>
#include <string.h>
#include <map>
@ -82,7 +81,7 @@ public:
const char *operator[](int ch) const
{
map<int, const char *>::const_iterator iter;
const char *retval = NULL;
const char *retval = nullptr;
if ((iter = this->vem_map.find(ch)) != this->vem_map.end()) {
retval = iter->second;
@ -94,9 +93,9 @@ public:
const char *operator[](const char *seq) const
{
map<string, const char *>::const_iterator iter;
const char *retval = NULL;
const char *retval = nullptr;
require(seq != NULL);
require(seq != nullptr);
if ((iter = this->vem_input_map.find(seq)) !=
this->vem_input_map.end()) {
@ -114,7 +113,7 @@ private:
static char area_buffer[1024];
char * area = area_buffer;
if (tgetent(NULL, "vt52") == ERR) {
if (tgetent(nullptr, "vt52") == ERR) {
perror("tgetent");
}
this->vem_map[KEY_UP] = tgetstr((char *)"ku", &area);
@ -122,7 +121,7 @@ private:
this->vem_map[KEY_RIGHT] = tgetstr((char *)"kr", &area);
this->vem_map[KEY_LEFT] = tgetstr((char *)"kl", &area);
this->vem_map[KEY_HOME] = tgetstr((char *)"kh", &area);
if (this->vem_map[KEY_HOME] == NULL) {
if (this->vem_map[KEY_HOME] == nullptr) {
this->vem_map[KEY_HOME] = "\x01";
}
this->vem_map[KEY_BACKSPACE] = "\010";
@ -132,11 +131,11 @@ private:
this->vem_map[KEY_END] = "\x05";
this->vem_map[KEY_SLEFT] = tgetstr((char *)"#4", &area);
if (this->vem_map[KEY_SLEFT] == NULL) {
if (this->vem_map[KEY_SLEFT] == nullptr) {
this->vem_map[KEY_SLEFT] = "\033b";
}
this->vem_map[KEY_SRIGHT] = tgetstr((char *)"%i", &area);
if (this->vem_map[KEY_SRIGHT] == NULL) {
if (this->vem_map[KEY_SRIGHT] == nullptr) {
this->vem_map[KEY_SRIGHT] = "\033f";
}
@ -145,7 +144,7 @@ private:
this->vem_input_map[tgetstr((char *)"ce", &area)] = "ce";
this->vem_input_map[tgetstr((char *)"kl", &area)] = "kl";
this->vem_input_map[tgetstr((char *)"kr", &area)] = "kr";
tgetent(NULL, getenv("TERM"));
tgetent(nullptr, getenv("TERM"));
};
/** Map of ncurses keycodes to VT52 escape sequences. */
@ -153,21 +152,12 @@ private:
map<string, const char *> vem_input_map;
};
vt52_curses::vt52_curses()
: vc_window(NULL),
vc_x(0),
vc_y(0),
vc_max_height(0),
vc_escape_len(0),
vc_map_buffer(0)
{ }
const char *vt52_curses::map_input(int ch, int &len_out)
{
const char *esc, *retval;
/* Check for an escape sequence, otherwise just return the char. */
if ((esc = vt52_escape_map::singleton()[ch]) != NULL) {
if ((esc = vt52_escape_map::singleton()[ch]) != nullptr) {
retval = esc;
len_out = strlen(retval);
}
@ -182,7 +172,7 @@ const char *vt52_curses::map_input(int ch, int &len_out)
len_out = 1;
}
ensure(retval != NULL);
ensure(retval != nullptr);
ensure(len_out > 0);
return retval;
@ -202,10 +192,26 @@ void vt52_curses::map_output(const char *output, int len)
this->vc_escape_len += 1;
this->vc_escape[this->vc_escape_len] = '\0';
if ((cap = vt52_escape_map::singleton()[this->vc_escape]) !=
nullptr) {
if (this->vc_expected_escape_len != -1) {
if (this->vc_escape_len == this->vc_expected_escape_len) {
auto& line_string = this->vc_line.get_string();
auto x_byte_index = utf8_char_to_byte_index(line_string, this->vc_x);
for (int esc_index = 0; esc_index < this->vc_escape_len; esc_index++) {
if (x_byte_index < this->vc_line.length()) {
line_string[x_byte_index] = this->vc_escape[esc_index];
} else {
this->vc_line.append(1, this->vc_escape[esc_index]);
}
x_byte_index += 1;
}
this->vc_x += 1;
this->vc_escape_len = 0;
}
} else if ((cap = vt52_escape_map::singleton()[this->vc_escape]) !=
nullptr) {
if (strcmp(cap, "ce") == 0) {
this->vc_line.erase(this->vc_x);
this->vc_line.erase_utf8_chars(this->vc_x);
this->vc_escape_len = 0;
}
else if (strcmp(cap, "kl") == 0) {
@ -222,7 +228,19 @@ void vt52_curses::map_output(const char *output, int len)
}
}
else {
switch (output[lpc]) {
auto next_ch = output[lpc];
auto seq_size = ww898::utf::utf8::char_size([next_ch]() {
return next_ch;
});
if (seq_size > 1) {
this->vc_escape[0] = next_ch;
this->vc_escape_len = 1;
this->vc_expected_escape_len = seq_size;
continue;
}
switch (next_ch) {
case STX:
this->vc_x = 0;
this->vc_line.clear();
@ -239,6 +257,7 @@ void vt52_curses::map_output(const char *output, int len)
case ESCAPE:
this->vc_escape[0] = ESCAPE;
this->vc_escape_len = 1;
this->vc_expected_escape_len = -1;
break;
case '\n':
@ -250,15 +269,19 @@ void vt52_curses::map_output(const char *output, int len)
this->vc_x = 0;
break;
default:
if (this->vc_x < this->vc_line.length()) {
this->vc_line.get_string()[this->vc_x] = output[lpc];
default: {
auto& line_string = this->vc_line.get_string();
auto x_byte_index = utf8_char_to_byte_index(line_string, this->vc_x);
if (x_byte_index < this->vc_line.length()) {
line_string[x_byte_index] = next_ch;
} else {
this->vc_line.append(1, output[lpc]);
this->vc_line.append(1, next_ch);
}
this->vc_x += 1;
break;
}
}
}
}
}
@ -269,4 +292,5 @@ void vt52_curses::do_update()
this->get_actual_y(), this->vc_left,
this->vc_line,
line_range{ 0, (int) this->vc_width });
wmove(this->vc_window, this->get_actual_y(), this->vc_left + this->vc_x);
}

View File

@ -54,8 +54,6 @@
class vt52_curses
: public view_curses {
public:
vt52_curses();
/** @param win The curses window this view is attached to. */
void set_window(WINDOW *win) { this->vc_window = win; };
@ -145,14 +143,15 @@ protected:
return retval;
};
WINDOW *vc_window; /*< The window that contains this view. */
WINDOW *vc_window{nullptr}; /*< The window that contains this view. */
int vc_left{0};
int vc_x; /*< The X position of the cursor. */
int vc_y; /*< The Y position of the cursor. */
int vc_max_height;
int vc_x{0}; /*< The X position of the cursor. */
int vc_y{0}; /*< The Y position of the cursor. */
int vc_max_height{0};
char vc_escape[16]; /*< Storage for escape sequences. */
int vc_escape_len; /*< The number of chars in vc_escape. */
char vc_map_buffer; /*<
int vc_escape_len{0}; /*< The number of chars in vc_escape. */
int vc_expected_escape_len{-1};
char vc_map_buffer{0}; /*<
* Buffer returned by map_input for trivial
* translations (one-to-one).
*/

158
src/ww898/cp_utf8.hpp Normal file
View File

@ -0,0 +1,158 @@
/*
* MIT License
*
* Copyright (c) 2017-2019 Mikhail Pilin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <cstdint>
#include <stdexcept>
namespace ww898 {
namespace utf {
// Supported combinations:
// 0xxx_xxxx
// 110x_xxxx 10xx_xxxx
// 1110_xxxx 10xx_xxxx 10xx_xxxx
// 1111_0xxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
// 1111_10xx 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
// 1111_110x 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
struct utf8 final
{
static size_t const max_unicode_symbol_size = 4;
static size_t const max_supported_symbol_size = 6;
static uint32_t const max_supported_code_point = 0x7FFFFFFF;
using char_type = uint8_t;
template<typename PeekFn>
static size_t char_size(PeekFn && peek_fn)
{
char_type const ch0 = std::forward<PeekFn>(peek_fn)();
if (ch0 < 0x80) // 0xxx_xxxx
return 1;
if (ch0 < 0xC0)
throw std::runtime_error("The utf8 first char in sequence is incorrect");
if (ch0 < 0xE0) // 110x_xxxx 10xx_xxxx
return 2;
if (ch0 < 0xF0) // 1110_xxxx 10xx_xxxx 10xx_xxxx
return 3;
if (ch0 < 0xF8) // 1111_0xxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
return 4;
if (ch0 < 0xFC) // 1111_10xx 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
return 5;
if (ch0 < 0xFE) // 1111_110x 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
return 6;
throw std::runtime_error("The utf8 first char in sequence is incorrect");
}
template<typename ReadFn>
static uint32_t read(ReadFn && read_fn)
{
char_type const ch0 = read_fn();
if (ch0 < 0x80) // 0xxx_xxxx
return ch0;
if (ch0 < 0xC0)
throw std::runtime_error("The utf8 first char in sequence is incorrect");
if (ch0 < 0xE0) // 110x_xxxx 10xx_xxxx
{
char_type const ch1 = read_fn(); if (ch1 >> 6 != 2) goto _err;
return (ch0 << 6) + ch1 - 0x3080;
}
if (ch0 < 0xF0) // 1110_xxxx 10xx_xxxx 10xx_xxxx
{
char_type const ch1 = read_fn(); if (ch1 >> 6 != 2) goto _err;
char_type const ch2 = read_fn(); if (ch2 >> 6 != 2) goto _err;
return (ch0 << 12) + (ch1 << 6) + ch2 - 0xE2080;
}
if (ch0 < 0xF8) // 1111_0xxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
{
char_type const ch1 = read_fn(); if (ch1 >> 6 != 2) goto _err;
char_type const ch2 = read_fn(); if (ch2 >> 6 != 2) goto _err;
char_type const ch3 = read_fn(); if (ch3 >> 6 != 2) goto _err;
return (ch0 << 18) + (ch1 << 12) + (ch2 << 6) + ch3 - 0x3C82080;
}
if (ch0 < 0xFC) // 1111_10xx 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
{
char_type const ch1 = read_fn(); if (ch1 >> 6 != 2) goto _err;
char_type const ch2 = read_fn(); if (ch2 >> 6 != 2) goto _err;
char_type const ch3 = read_fn(); if (ch3 >> 6 != 2) goto _err;
char_type const ch4 = read_fn(); if (ch4 >> 6 != 2) goto _err;
return (ch0 << 24) + (ch1 << 18) + (ch2 << 12) + (ch3 << 6) + ch4 - 0xFA082080;
}
if (ch0 < 0xFE) // 1111_110x 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
{
char_type const ch1 = read_fn(); if (ch1 >> 6 != 2) goto _err;
char_type const ch2 = read_fn(); if (ch2 >> 6 != 2) goto _err;
char_type const ch3 = read_fn(); if (ch3 >> 6 != 2) goto _err;
char_type const ch4 = read_fn(); if (ch4 >> 6 != 2) goto _err;
char_type const ch5 = read_fn(); if (ch5 >> 6 != 2) goto _err;
return (ch0 << 30) + (ch1 << 24) + (ch2 << 18) + (ch3 << 12) + (ch4 << 6) + ch5 - 0x82082080;
}
throw std::runtime_error("The utf8 first char in sequence is incorrect");
_err: throw std::runtime_error("The utf8 slave char in sequence is incorrect");
}
template<typename WriteFn>
static void write(uint32_t const cp, WriteFn && write_fn)
{
if (cp < 0x80) // 0xxx_xxxx
write_fn(static_cast<char_type>(cp));
else if (cp < 0x800) // 110x_xxxx 10xx_xxxx
{
write_fn(static_cast<char_type>(0xC0 | cp >> 6));
goto _1;
}
else if (cp < 0x10000) // 1110_xxxx 10xx_xxxx 10xx_xxxx
{
write_fn(static_cast<char_type>(0xE0 | cp >> 12));
goto _2;
}
else if (cp < 0x200000) // 1111_0xxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
{
write_fn(static_cast<char_type>(0xF0 | cp >> 18));
goto _3;
}
else if (cp < 0x4000000) // 1111_10xx 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
{
write_fn(static_cast<char_type>(0xF8 | cp >> 24));
goto _4;
}
else if (cp < 0x80000000) // 1111_110x 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
{
write_fn(static_cast<char_type>(0xFC | cp >> 30));
goto _5;
}
else
throw std::runtime_error("Tool large UTF8 code point");
return;
_5: write_fn(static_cast<char_type>(0x80 | (cp >> 24 & 0x3F)));
_4: write_fn(static_cast<char_type>(0x80 | (cp >> 18 & 0x3F)));
_3: write_fn(static_cast<char_type>(0x80 | (cp >> 12 & 0x3F)));
_2: write_fn(static_cast<char_type>(0x80 | (cp >> 6 & 0x3F)));
_1: write_fn(static_cast<char_type>(0x80 | (cp & 0x3F)));
}
};
}}

View File

@ -330,12 +330,12 @@ TESTS = \
test_sql_str_func.sh \
test_sql_time_func.sh \
test_data_parser.sh \
test_pretty_print.sh
test_pretty_print.sh \
test_vt52_curses.sh
DISABLED_TESTS = \
test_top_status \
test_view_colors.sh \
test_vt52_curses.sh
test_view_colors.sh
if HAVE_LIBCURL
TESTS += \

View File

@ -35,7 +35,9 @@
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <locale.h>
#include "base/lnav_log.hh"
#include "view_curses.hh"
#include "vt52_curses.hh"
@ -64,11 +66,14 @@ int main(int argc, char *argv[])
{
int lpc, c, fd, retval = EXIT_SUCCESS;
vt52_curses vt;
setenv("LANG", "en_US.utf-8", 1);
setlocale(LC_ALL, "");
fd = open("/tmp/lnav.err", O_WRONLY|O_CREAT|O_APPEND, 0666);
dup2(fd, STDERR_FILENO);
close(fd);
fprintf(stderr, "startup\n");
lnav_log_file = stderr;
while ((c = getopt(argc, argv, "y:")) != -1) {
switch (c) {
@ -81,16 +86,16 @@ int main(int argc, char *argv[])
for (lpc = 0; lpc < 1000; lpc++) {
int len;
assert(vt.map_input(random(), len) != NULL);
assert(vt.map_input(random(), len) != nullptr);
assert(len > 0);
}
tgetent(NULL, "vt52");
tgetent(nullptr, "vt52");
{
static const char *CANNED_INPUT[] = {
"abc",
"Gru\xC3\x9F",
"\r",
tgetstr((char *)"ce", NULL),
tgetstr((char *)"ce", nullptr),
"de",
"\n",
"1\n",
@ -103,7 +108,7 @@ int main(int argc, char *argv[])
"8\n",
"9\n",
"abc",
"\x2",
"\x02",
"\a",
"ab\bcdef",
0

View File

@ -210,7 +210,7 @@ static void dump_memory(FILE *dst, const char *src, int len)
int lpc;
for (lpc = 0; lpc < len; lpc++) {
fprintf(dst, "%02x", src[lpc]);
fprintf(dst, "%02x", src[lpc] & 0xff);
}
}

View File

@ -1,4 +1,5 @@
#include <stdlib.h>
#include "config.h"
#define _XOPEN_SOURCE_EXTENDED 1
#include <locale.h>
@ -19,6 +20,7 @@
int main(int argc, char *argv[])
{
setenv("LANG", "en_US.utf-8", 1);
setlocale(LC_ALL, "");
WINDOW *stdscr = initscr();

View File

@ -1,38 +1,38 @@
sleep 2.051763
write 31
sleep 0.311858
write 32
sleep 0.295852
write 33
sleep 0.319916
write 34
sleep 0.327742
write 35
sleep 0.328183
write 36
sleep 0.335896
write 37
sleep 0.360085
write 38
sleep 0.336000
write 39
sleep 0.376058
write 30
sleep 0.840149
write 61
sleep 0.783892
write 62
sleep 0.415990
write 63
sleep 0.280001
write 64
sleep 0.191901
write 65
sleep 0.295949
write 66
sleep 0.775841
write 67
sleep 0.399883
write 68
sleep 0.304016
write 69
sleep 0.867286
write 0d
sleep 0.141596
write 0d
sleep 0.188837
write 0d
sleep 0.177586
write 0d
sleep 0.159950
write 0d
sleep 0.158958
write 0d
sleep 0.164105
write 0d
sleep 0.176968
write 0d
sleep 0.165942
write 0d
sleep 0.187011
write 0d
sleep 0.167987
write 0d
sleep 0.173959
write 0d
sleep 0.176091
write 0d
sleep 0.180728
write 0d
sleep 0.172983
write 0d
sleep 0.167819
write 0d
sleep 0.165876
write 0d
sleep 0.175857
write 0d
sleep 0.183068
write 0d

View File

@ -1,38 +1,39 @@
read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a616263
# write 31
read 1b29301b371b5b3f3437681b5b313b3234721b5b6d1b5b346c1b5b481b5b324a477275c39f
# write 0d
read 0d
# write 32
# write 0d
read 1b5b4a
# write 33
# write 0d
read 6465
# write 34
# write 0d
read 0d1b5b4a
# write 35
# write 0d
read
# write 36
# write 0d
read
# write 37
# write 0d
read
# write 38
# write 0d
read
# write 39
# write 0d
read
# write 30
# write 0d
read
# write 61
# write 0d
read
# write 62
# write 0d
read
# write 63
# write 0d
read
# write 64
# write 0d
read 616263
# write 65
# write 0d
read 0d1b5b4a
# write 66
# write 0d
read 07
# write 67
# write 0d
read 6163646566
# write 68
# write 0d
read
# write 69
# write 0d
read 1b5b32343b31481b5b324a1b5b3f34376c1b380d1b5b3f316c1b3e