Как дешифровать код ctk.c победителя IOCCC 2001 года?

Я видел запутанный код ctk.c, но как я могу начать его обфусцировать?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#define m(b)a=b;z=*a;while(*++a){y=*a;*a=z;z=y;}
#define h(u)G=u<<3;printf("\e[%uq",l[u])
#define c(n,s)case n:s;continue
char x[]="((((((((((((((((((((((",w[]=
"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";char r[]={92,124,47},l[]={2,3,1
,0};char*T[]={"  |","  |","%\\|/%"," %%%",""};char d=1,p=40,o=40,k=0,*a,y,z,g=
-1,G,X,**P=&T[4],f=0;unsigned int s=0;void u(int i){int n;printf(
"\233;%uH\233L%c\233;%uH%c\233;%uH%s\23322;%uH@\23323;%uH \n",*x-*w,r[d],*x+*w
,r[d],X,*P,p+=k,o);if(abs(p-x[21])>=w[21])exit(0);if(g!=G){struct itimerval t=
{0,0,0,0};g+=((g<G)<<1)-1;t.it_interval.tv_usec=t.it_value.tv_usec=72000/((g>>
3)+1);setitimer(0,&t,0);f&&printf("\e[10;%u]",g+24);}f&&putchar(7);s+=(9-w[21]
)*((g>>3)+1);o=p;m(x);m(w);(n=rand())&255||--*w||++*w;if(!(**P&&P++||n&7936)){
while(abs((X=rand()%76)-*x+2)-*w<6);++X;P=T;}(n=rand()&31)<3&&(d=n);!d&&--*x<=
*w&&(++*x,++d)||d==2&&++*x+*w>79&&(--*x,--d);signal(i,u);}void e(){signal(14,
SIG_IGN);printf("\e[0q\ecScore: %u\n",s);system("stty echo -cbreak");}int main
(int C,char**V){atexit(e);(C<2||*V[1]!=113)&&(f=(C=*(int*)getenv("TERM"))==(
int)0x756E696C||C==(int)0x6C696E75);srand(getpid());system("stty -echo cbreak"
);h(0);u(14);for(;;)switch(getchar()){case 113:return 0;case 91:case 98:c(44,k
=-1);case 32:case 110:c(46,k=0);case 93:case 109:c(47,k=1);c(49,h(0));c(50,h(1
));c(51,h(2));c(52,h(3));}} 

http://www.ioccc.org/2001/ctk.hint:

This is a game based on an Apple ][ Print Shop Companion easter
egg named 'DRIVER', in which the goal is to drive as fast as
you can down a long twisty highway without running off the
road.  Use ',./', '[ ]', or 'bnm' to go left, straight, and
right respectively. Use '1234' to switch gears. 'q' quits. The
faster you go and the thinner the road is, the more points you
get. Most of the obfuscation is in the nonsensical if statements
among other things. It works best on the Linux console: you
get engine sound (!) and the * Lock keyboard lights tell you
what gear you're in (none lit=4th).  The 'q' argument (no
leading '-') will silence the sound. It won't work on a terminal
smaller than 80x24, but it works fine with more (try it in an
XTerm with the "Unreadable" font and the window maximized
vertically!).

1 ответ

Решение

1-й шаг

С помощью:

sed -e'/#include/d' ctk.c | gcc -E - | sed -e's/;/;\n/g' -e's/}/}\n/g' -e '/^#/d' | indent

Я смог сгенерировать следующий вывод, который, хотя и не идеальный, уже кажется читаемым намного лучше:

char x[] = "((((((((((((((((((((((", w[] =
  "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
char r[] = { 92, 124, 47 }

, l[] =
{
2, 3, 1, 0}

;
char *T[] = { "  |", "  |", "%\\|/%", " %%%", "" }

;
char d = 1, p = 40, o = 40, k = 0, *a, y, z, g = -1, G, X, **P = &T[4], f = 0;
unsigned int s = 0;
void
u (int i)
{
  int n;
  printf ("\233;
%uH\233L%c\233;
%uH%c\233;
%uH%s\23322;
%uH@\23323;
%uH \n", *x - *w, r[d], *x + *w, r[d], X, *P, p += k, o);
  if (abs (p - x[21]) >= w[21])
    exit (0);
  if (g != G)
    {
      struct itimerval t = { 0, 0, 0, 0 }
      ;
      g += ((g < G) << 1) - 1;
      t.it_interval.tv_usec = t.it_value.tv_usec = 72000 / ((g >> 3) + 1);
      setitimer (0, &t, 0);
      f && printf ("\e[10;
%u]", g + 24);
    }
  f && putchar (7);
  s += (9 - w[21]) * ((g >> 3) + 1);
  o = p;
  a = x;
  z = *a;
  while (*++a)
    {
      y = *a;
      *a = z;
      z = y;
    }
  ;
  a = w;
  z = *a;
  while (*++a)
    {
      y = *a;
      *a = z;
      z = y;
    }
  ;
  (n = rand ()) & 255 || --*w || ++*w;
  if (!(**P && P++ || n & 7936))
    {
      while (abs ((X = rand () % 76) - *x + 2) - *w < 6);
      ++X;
      P = T;
    }
  (n = rand () & 31) < 3 && (d = n);
  !d && --*x <= *w && (++*x, ++d) || d == 2 && ++*x + *w > 79 && (--*x, --d);
  signal (i, u);
}

void
e ()
{
  signal (14, SIG_IGN);
  printf ("\e[0q\ecScore: %u\n", s);
  system ("stty echo -cbreak");
}

int main (int C, char **V)
{
  atexit (e);
  (C < 2 || *V[1] != 113)
    && (f = (C = *(int *) getenv ("TERM")) == (int) 0x756E696C
    || C == (int) 0x6C696E75);
  srand (getpid ());
  system ("stty -echo cbreak");
  G = 0 << 3;
  printf ("\e[%uq", l[0]);
  u (14);
  for (;;)
    switch (getchar ())
      {
      case 113:
    return 0;
      case 91:
      case 98:
      case 44:
    k = -1;
    continue;
      case 32:
      case 110:
      case 46:
    k = 0;
    continue;
      case 93:
      case 109:
      case 47:
    k = 1;
    continue;
      case 49:
    G = 0 << 3;
    printf ("\e[%uq", l[0]);
    continue;
      case 50:
    G = 1 << 3;
    printf ("\e[%uq", l[1]);
    continue;
      case 51:
    G = 2 << 3;
    printf ("\e[%uq", l[2]);
    continue;
      case 52:
    G = 3 << 3;
    printf ("\e[%uq", l[3]);
    continue;
      }
}

... и сейчас?

Я не думаю, что гораздо больше автоматизированный процесс сможет выполнить в этот момент, так как термин "более" читаемый или "менее" читаемый отныне может зависеть от конкретных предпочтений читателя.

Один шаг, который можно выполнить, - это удалить escape-последовательности из строк и поместить их где-то отдельно. Как оказывается, весь

char l[] = {2, 3, 1, 0}

не имеет никакой другой цели, кроме использования в escape-последовательностях основного цикла:

printf ("\e[%uq", l[0]);

и так далее. Посмотрим на их значение:

ESC [ 0 q: clear all LEDs
ESC [ 1 q: set Scroll Lock LED
ESC [ 2 q: set Num Lock LED
ESC [ 3 q: set Caps Lock LED

в зависимости от вкуса вы можете обменяться ими с помощью макроса или вызова функции, более значимых для вас, как clear_all_LEDs и так далее.

Я сильно сомневаюсь, что машина согласится, что это упрощение. Оказывается, что весь основной цикл, похоже, работает с ключами, введенными пользователем, поэтому, возможно, превращение чисел в соответствующие им символы может улучшить читабельность, например, при замене:

case 113:
  return 0;
case 91:
case 98:
case 44:
  k = -1;
// ...
case 49:
  G = 0 << 3;
  printf ("\e[%uq", l[0]);

с чем-то вроде:

case 'q':
  return 0;
case '[':
case 'b':
case ',':
  k = -1;
// ...
case '1':
  G = 0 << 3;
  set_Num_Lock_LED ();

Ох, и хотя мы уже находимся в этом, почему бы нам не захотеть сменить название на это довольно странное G в gear, Опять же, я сильно сомневаюсь, что автоматизированный процесс нашел бы переименование из G в gear лучше, чем переименовать его butterfly, Ну, может быть, это даже не так.

При украшении имен, возможно, эта функция ссылается на один u другой кандидат:

u (14);

с более значимым именем update наверное. И как мы уже включили <signal.h> почему бы нам не деобфусцировать код дальше, заменив 14 с SIGALRM как это:

upadate (SIGALRM);

Как вы можете видеть, "deobfuscating" здесь требует прямо противоположного шага, сделанного ранее. На этот раз замена расширения макросом. Как машина попытается решить, какая из них более полезна?

Еще одно место, где мы могли бы захотеть заменить пустое число на что-то другое, это в функции обновления:

f && putchar (7);

Почему бы не заменить 7 с \a как это окажется то же самое в конце. Может быть, мы должны даже изменить голое f с чем-то более "значимым".

Я снова голосую против butterfly но лучше бы это назвать play_sound:

if (play_sound)
   putchar ('\a');

может быть более читаемая версия, которую мы ищем. Конечно, мы не должны забывать заменять f во всех других местах. Тот, кто находится в начале нашей основной функции, является таким преступником:

Святой беспорядок

int main (int C, char **V)
{
  atexit (e);
  (C < 2 || *V[1] != 113)
    && (f = (C = *(int *) getenv ("TERM")) == (int) 0x756E696C
    || C == (int) 0x6C696E75);

Пока счастливо переименовываю f в play_sound а также e до - нет, до сих пор нет butterfly На этот раз я лучше назову это: end мы заметили, что сигнатура функции выглядит немного странно с точки зрения соглашений об именах: argc вместо C а также argv вместо V казалось бы более обычным здесь. Таким образом, давая нам:

int main (int argc, char* argv[])
{
  atexit (end);
  (argc < 2 || *argv[1] != 113)
    && (playsound = (argc = *(int *) getenv ("TERM")) == (int) 0x756E696C
    || argc == (int) 0x6C696E75);

Поскольку это все еще не красота, мы спрашиваем нашего парня по стандартам, и он сообщает нам, что это вполне нормально заменить

(A || B) && (C)

с

if (A || B) { C }

а также

E = (x=F)==H || x==I

с

x = F; 
if (x==H || x==I) 
  A=1; 
else 
  A=0;` 

Так что, возможно, это должна быть более читаемая версия всего кода:

if (argc < 2 || *argv[1] != 'q') {
   argc = *(int*) getenv ("TERM");
   if (argc == (int) 0x756E69 || argc == (int) 0x6C696E75))
     play_sound = 1;
   /* skip the else brach here as play_sound is alredy initialized to 0 */
}

Теперь еще один парень появляется и начинает информировать нас, что в зависимости от того, что называется endianness, странные цифры 0x6C696E75 и 0x756E69, если их хранить в памяти, (при интерпретации необработанных байтовых значений как кода ASCII) выглядят так: "linu" или же "unil", Один из них "unil" для одного типа архитектуры, а "linu" - для другого, а другой - для другой архитектуры с другим порядком байтов.

Итак, внимательнее посмотрим, что по сути здесь происходит:

  • мы получаем указатель на строку из getenv ("TERM"), которую мы вводим в указатель на int перед разыменованием его, тем самым ведя битовый шаблон, хранящийся в расположении строки, как int.
  • затем мы сравниваем это значение с тем, которое мы получили бы, если бы выполнили то же самое с "unil" или "linu", хранящимися в этом конкретном месте.

Вероятно, мы просто хотим проверить, установлена ​​ли переменная окружения TERM на "linux", поэтому наша деобфусцированная версия может захотеть выполнить сравнение строк здесь.

С другой стороны, мы не можем быть уверены, что разрешение на воспроизведение звука терминалами с названиями, начинающимися с "unil", может быть особой особенностью этого программного обеспечения, поэтому я решил, вероятно, лучше оставить его нетронутым.

Что теперь?

При переименовании и перекодировании имен и значений переменных эти странные символьные массивы могут стать нашими следующими жертвами. Следующий беспорядок не выглядит слишком хорошим:

char x[] = "((((((((((((((((((((((", w[] =
  "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
char r[] = { 92, 124, 47 };

Так что, возможно, они могут быть изменены на:

char x_offset[] = {
  40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
  40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
  40, 40, 0 };

char width[] = {
  8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
  8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
  8, 8, 0 };

const char border[] = "\\|/";

Как видите, я просто выбрал способ, которым значения описываются между x как строковая константа для x, записанная в виде массива, так что назначение значений, хранящихся здесь, показалось мне немного более понятным.

Хотя, с другой стороны, я изменил способ r записан как раз в обратном направлении, и опять мне это показалось намного понятнее.

Охотясь за всеми этими ссылками на x, w а также r время может быть использовано для переименования p а также o извините, нет butterfly - pos а также old_pos во время переименования s в score,

Изменение например:

  s += (9 - w[21]) * ((g >> 3) + 1);
  o = p;
  a = x;
  z = *a;
  while (*++a)
    {
      y = *a;
      *a = z;
      z = y;
    }
  ;
  a = w;
  z = *a;
  while (*++a)
    {
      y = *a;
      *a = z;
      z = y;
    }
  ;

чтобы:

  /* update score */
  score += (9 - width[NEXT_LINE]) * ((g >> 3) + 1);
  old_pos = pos;

  /* shift x_offset */
  a = x_offset;
  z = *a;
  while (*++a) {
    y = *a;
    *a = z;
    z = y;
  };

  /* shift width */
  a = width;
  z = *a;
  while (*++a) {
    y = *a;
    *a = z;
    z = y;
  };

Помимо возможности превратить его в какой-то другой вид цикла, для обеих функций сдвига не так много возможностей для украшения, поэтому, возможно, добавление соответствующего комментария - это максимум, что вы можете сделать. Удаление магического числа 21 может быть другая идея NEXT_LINE не кажется худшим выбором здесь.

Переменная, помеченная одним символом g все еще не выглядит слишком хорошо. Но переименовав его в что-то вроде update_interval также есть возможность исключить еще одну странную escape-последовательность терминала:

 if (g != G)
    {
      struct itimerval t = { 0, 0, 0, 0 }
      ;
      g += ((g < G) << 1) - 1;
      t.it_interval.tv_usec = t.it_value.tv_usec = 72000 / ((g >> 3) + 1);
      setitimer (0, &t, 0);
      f && printf ("\e[10;
%u]", g + 24);
    }

Может быть, выглядит немного более запутанным, чем:

  /* update simulation speed */
  if (update_interval != gear) {
    struct itimerval t = { 0, 0, 0, 0 }  ;
      update_interval += ((update_interval < gear) << 1) - 1;
      t.it_interval.tv_usec = t.it_value.tv_usec = 72000 / ((update_interval >> 3) + 1);
      setitimer (0, &t, 0);
      if (play_sound)
        change_bell_frequency (update_interval + 24);
  }

Последние исправления

Несмотря на то, что код должен выглядеть намного более читабельным к настоящему времени, все еще остаются некоторые неприятные части:

!d && --*x <= *w && (++*x, ++d) || d == 2 && ++*x + *w > 79 && (--*x, --d);

Выбор другого (надеюсь) более значимого имени для d и нарушая приоритет оператора, вы можете получить что-то вроде:

  if (curve == CURVE_LEFT) {
    --*x_offset;
    if (*x_offset < *width) {
       ++*x_offset;
       curve = CURVE_NONE;
    }
  }
  else if (curve == CURVE_RIGHT) {
    ++*x_offset;
    if (*x_offset + *width > 79) {
      --*x_offsett;
      curve = CURVE_NONE;
    }
  } 

вместо этого добавив соответствующие макросы для всех тех, CURVE_... s.

Теперь есть еще те X, P а также T имена, висящие вокруг, которые также могут быть изменены. Поскольку это делает его назначение также немного лучше видимым в коде, я решил изменить порядок строк: T что я переименовал в tree что означает, что расчет также должен быть исправлен. В общем, это из:

char *T[] = { "  |", "  |", "%\\|/%", " %%%", "" };
char X, **P = &T[4];

// ...

  if (!(**P && P++ || n & 7936))
    {
      while (abs ((X = rand () % 76) - *x + 2) - *w < 6);
      ++X;
      P = T;
    }

Для чего-то вроде:

char *tree[] = {
  "",
  " %%%",
  "%\\|/%",
  "  |",
  "  |",
};

char **tree_line = tree;
char tree_position;

// ...

  /* update tree line pointer */
  if (!(**tree_line && tree_line-- || n & 7936)) {
    /* find the right spot to grow */
    while (abs ((tree_position = rand () % 76) - *x_offset + 2) - *width < 6)
      ;
    ++tree_position;
    tree_line = &tree[4];
  }

Сохраняя лучшую часть до конца

Хотя мне уже кажется, что код выглядит намного красивее, сейчас все еще не хватает одной части. Это тот, который делает весь вывод. Именно об этой строке я говорю:

 printf ("\233;%uH\233L%c\233;%uH%c\233;%uH%s\23322;%uH@\23323;%uH \n",
      *x - *w, r[d], *x + *w, r[d], X, *P, p += k, o); 

Это, помимо того, что он выглядел довольно трудно читаемым, было даже запутанным для компьютера, чтобы получить какой-либо полезный результат.

Я пробовал много разных вещей, работающих в других эмуляторах терминала, меняя настройки терминала и переключая локали туда и обратно без успеха.

Таким образом, помимо того, что этот вид запутывания казался более совершенным, поскольку он даже сбивает с толку мой компьютер, я до сих пор не могу сказать, какой трюк автор намеревался здесь.

Восьмеричный код \233 имеет тот же битовый шаблон, что и escape-символ (\033) с 8-м битом, установленным дополнительно, что, вероятно, каким-то образом связано с эффектом, который был задуман здесь. К сожалению, как я уже сказал, это не сработало для меня.

К счастью, escape-последовательности все еще казались достаточно легкими для угадывания, поэтому я предложил следующую замену:

pos + = move_x,

  /* draw street */
  printf ("\e[1;%uH" "\e[L" "%c"
          "\e[1;%uH" "%c",
          *x_offset - *width, border[curve],
          *x_offset + *width, border[curve]);
  /* draw tree */
  printf ("\e[1;%uH" "%s",
          tree_position, *tree_line);

  /* redraw car */
  printf ("\e[22;%uH" "@"
          "\e[23;%uH" " " "\n",
          pos,
          old_pos);  

Взятие рисования на отдельные, чтобы (надеюсь) сделать их немного более читабельными. Фактическая строка и предыдущая строка по-прежнему жестко закодированы здесь, как и в оригинальной версии. Возможно, извлечение их оттуда, как показано ниже, даже улучшит читабельность:

  /* draw street */
  printf ("\e[1;%uH" "\e[L" "%c"
          "\e[1;%uH" "%c",
          *x_offset - *width, border[curve],
          *x_offset + *width, border[curve]);
  /* draw tree */
  printf ("\e[1;%uH" "%s",
          tree_position, *tree_line);

  /* redraw car */
  printf ("\e[%u;%uH" "@"
          "\e[%u;%uH" " " "\n",
          NEXT_LINE +1, pos,
          NEXT_LINE +2, old_pos);

Это, наконец, привело меня к первой пригодной для использования версии, которую я потом "много тестировал". Хотя, вероятно, не на 100% современный уровень, он все еще кажется очень захватывающим.

Последние слова

Вот финальная версия без каких-либо объяснений, с которой я пришел. Как вы увидите, я не реализовал функции установки светодиодов и функцию очистки экрана, но не должно быть сложно найти необходимые escape-последовательности, разбросанные по запутанной версии. На самом деле я уже упоминал последовательности светодиодов в этом посте. Чтобы очистить экран, нужно нажать "\e[0q". Счастливого взлома.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

#define NEXT_LINE 21

#define CURVE_LEFT 0
#define CURVE_NONE 1
#define CURVE_RIGHT 2

char x_offset[] = {
  40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
  40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
  40, 40, 0 };

char width[] = {
  8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
  8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
  8, 8, 0 };

const char border[] = "\\|/";

void change_bell_frequency () {}
void clear_screen () {}
void clear_all_LEDs () {}
void set_Num_Lock_LED () {}
void set_Scroll_lock_LED () {}
void set_Caps_Lock_LED () {}



char *tree[] = {
  "",
  " %%%",
  "%\\|/%",
  "  |",
  "  |",
};


char **tree_line = tree;
char tree_position;

char curve = CURVE_NONE;
char *a, y, z;

char move_x = 0;
char update_interval = -1;

char pos = 40;
char old_pos = 40;

char play_sound = 0;
char gear;

unsigned int score = 0;

void move (char x, char y) {
  printf ("\e[%u;%uH", x, y);
}

void insert () {
  printf ("\e[L");
}

void update (int i) {
  int n;

  pos += move_x,

  /* draw street */
  printf ("\e[1;%uH" "\e[L" "%c"
          "\e[1;%uH" "%c",
          *x_offset - *width, border[curve],
          *x_offset + *width, border[curve]);
  /* draw tree */
  printf ("\e[1;%uH" "%s",
          tree_position, *tree_line);

  /* redraw car */
  printf ("\e[%u;%uH" "@"
          "\e[%u;%uH" " " "\n",
          NEXT_LINE + 1, pos,
          NEXT_LINE +2, old_pos);

  /* did we leave the road ? */
  if (abs (pos - x_offset[NEXT_LINE]) >= width[NEXT_LINE])
    exit (0);

  /* update simulation speed */
  if (update_interval != gear) {
    struct itimerval t = { 0, 0, 0, 0 }  ;
      update_interval += ((update_interval < gear) << 1) - 1;
      t.it_interval.tv_usec = t.it_value.tv_usec = 72000 / ((update_interval >> 3) + 1);
      setitimer (0, &t, 0);
      if (play_sound)
        change_bell_frequency (update_interval + 24);
  }

  /* play sound */
  if (play_sound)
    putchar ('\a');

  /* update score */
  score += (9 - width[NEXT_LINE]) * ((update_interval >> 3) + 1);
  old_pos = pos;

  /* shift x_offset */
  a = x_offset;
  z = *a;
  while (*++a) {
    y = *a;
    *a = z;
    z = y;
  };

  /* shift width */
  a = width;
  z = *a;
  while (*++a) {
    y = *a;
    *a = z;
    z = y;
  };

  /* generate new road */
  n = rand ();

  if (!(n & 255) && *width > 1)
    --*width;

  /* set tree line pointer */
  if (!(**tree_line && tree_line-- || n & 7936)) {
    /* find the right spot to grow */
    while (abs ((tree_position = rand () % 76) - *x_offset + 2) - *width < 6)
      ;
    ++tree_position;
    tree_line = &tree[4];
  }

  /* new offset */
  n = rand () & 31;
  if (n < 3)
    curve = n;

  if (curve == CURVE_LEFT) {
    --*x_offset;
    if (*x_offset <= *width) {
      ++*x_offset;
      curve = CURVE_NONE;
    }
  }
  else if (curve == CURVE_RIGHT) {
    ++*x_offset;
    if (*x_offset + *width > 79) {
      --*x_offset;
      curve = CURVE_NONE;
    }
  }

  signal (SIGALRM, update);
}


void end () {
  signal (SIGALRM, SIG_IGN);
  clear_all_LEDs ();
  clear_screen ();
  printf ("Score: %u\n", score);
  system ("stty echo -cbreak");
}


int main (int argc, char **argv) {
  atexit (end);

  if (argc < 2 || *argv[1] != 'q') {
    argc = *(int*) getenv ("TERM");
    if (argc == (int) 0x6C696E75 || argc == (int) 0x756E696C)
      play_sound = 1;
  }

  srand (getpid ());
  system ("stty -echo cbreak");
  gear = 0 << 3;

  clear_all_LEDs ();
  update (14);
  for (;;)
    switch (getchar ())
      {
        case 'q':
          return 0;
        case '[':
        case 'b':
        case ',':
          move_x = -1;
          continue;
        case ' ':
        case 'n':
        case '.':
          move_x = 0;
          continue;
        case ']':
        case 'm':
        case '/':
          move_x = 1;
          continue;
        case '1':
          gear = 0 << 3;
          set_Num_Lock_LED ();
          continue;
        case '2':
          gear = 1 << 3;
          set_Caps_Lock_LED ();
          continue;
        case '3':
          gear = 2 << 3;
          set_Scroll_lock_LED ();
          continue;
        case '4':
          gear = 3 << 3;
          clear_all_LEDs ();
          continue;
      }
}
Другие вопросы по тегам