-
-
Save Priler/336b8123321b71ca0f8f05b96419cf7e to your computer and use it in GitHub Desktop.
| /* Лол, код из архива - файл датируется 2017 годом. | |
| Написан был после недели изучения C++. | |
| Реализует серверную часть чата на клиенте. | |
| Код не полный, писался на скорую руку - так сказать proof of concept. | |
| О, и да - семафоры это круто :3 */ | |
| #include <iostream> | |
| #include <string> | |
| #include <stdio.h> | |
| #include <cstring> | |
| #include <stdlib.h> | |
| #include <fstream> | |
| #include <iomanip> | |
| using namespace std; | |
| //Vars | |
| struct object_arguments { | |
| int arg_id; | |
| int arg_type; | |
| int arg_length; | |
| std::string arg; | |
| std::string type; | |
| struct object_arguments* next; | |
| }; | |
| struct connection_node { | |
| int id; //Connection id | |
| string type; //Node type | |
| string flag; //Node flag | |
| string dec_message; //Decoded message | |
| string enc_message; //Encoded message | |
| int base_params[5]; //Base params array {message_length, sequence_id, command_id, obj_type, obj_id, arg_count} | |
| struct object_arguments* arguments;//Message arguments, decoded as nodes | |
| struct connection_node* next; //Next node vars | |
| }; | |
| int cid = 1; //Connection starter id | |
| typedef struct connection_node *Cnodes; //Connections nodes list, p.s. starter node | |
| typedef struct object_arguments *Anodes; //Arguments defination | |
| //Functions | |
| /**************************************Help_Functions*************************************/ | |
| /*StringToChar_Function*/ | |
| char * StringToChar(std::string in_string) | |
| { | |
| std::string str (in_string); | |
| char * cstr = new char [str.length()+1]; | |
| std::strcpy (cstr, str.c_str()); | |
| return cstr; | |
| } | |
| /*To10_Function*/ | |
| int To10(std::string in_16) | |
| { | |
| char *input = StringToChar(in_16); | |
| char *next; | |
| long int value = strtol(input, &next, 16); | |
| return value; | |
| } | |
| /*To16_Function*/ | |
| string To16(int in_10) | |
| { | |
| char c[100]; | |
| int n=in_10; | |
| sprintf(c,"%X",n); | |
| return c; | |
| } | |
| /*Print_Array_Function*/ | |
| void Print_Array(int array[5]) | |
| { | |
| cout << "Array_Print:::" << endl << "---" << endl; | |
| cout << "message_length : " << array[0] << endl; | |
| cout << "sequence_id : " << array[1] << endl; | |
| cout << "command_id : " << array[2] << endl; | |
| cout << "obj_type : " << array[3] << endl; | |
| cout << "obj_id : " << array[4] << endl; | |
| cout << "arg_count : " << array[5] << endl; | |
| } | |
| /*FileGetContens_Function*/ | |
| std::string FileGetContents(std::string file_path) | |
| { | |
| ifstream F; | |
| F.open(StringToChar(file_path), ios::in); | |
| std::string content; | |
| F>>content; | |
| return content; | |
| } | |
| /*FillString_Function*/ | |
| std::string FillString(std::string base_string, std::string filler, int count, bool fill_from_left) | |
| { | |
| std::string result_string = base_string; | |
| int fill_count = count - base_string.length(); | |
| if (fill_count <= 0) | |
| { | |
| //do nothing here | |
| } | |
| else | |
| { | |
| std::string new_result = base_string; | |
| if (fill_from_left == true) | |
| { | |
| //fill it from left side | |
| for (int i = 0; i < fill_count; i++) | |
| { | |
| new_result = filler + new_result; | |
| } | |
| } | |
| else | |
| { | |
| //fill it from right side | |
| for (int i = 0; i < fill_count; i++, fill_count--) | |
| { | |
| new_result += filler; | |
| } | |
| } | |
| result_string = new_result; | |
| } | |
| return result_string; | |
| } | |
| /**************************************Connections_Nodes_Functions*************************************/ | |
| /*Init_Head_Node_Function*/ | |
| Cnodes Init_Head_Node(void) | |
| { | |
| Cnodes head_node=NULL; | |
| head_node=new struct connection_node; | |
| head_node->id=0; | |
| head_node->type="head"; | |
| head_node->flag="in_process"; | |
| head_node->dec_message=""; | |
| head_node->enc_message=""; | |
| for(int i = 0; i<(sizeof(head_node->base_params)/sizeof(int));i++) | |
| { | |
| head_node->base_params[i]=0; | |
| } | |
| head_node->arguments=NULL; | |
| head_node->next=NULL; | |
| return head_node; | |
| } | |
| /*Init_Head_Anode_Function*/ | |
| Anodes Init_Head_Anode(void) | |
| { | |
| Anodes head_anode = NULL; | |
| head_anode = new struct object_arguments; | |
| head_anode->arg_id = 0; | |
| head_anode->arg_type = 0; | |
| head_anode->arg_length = 0; | |
| head_anode->arg = ""; | |
| head_anode->type = "head"; | |
| head_anode->next = NULL; | |
| return head_anode; | |
| } | |
| /*Get_Last_Node_Function*/ | |
| Cnodes Get_Last_Node(Cnodes &starter_node) | |
| { | |
| bool not_final = true; | |
| Cnodes current_node = starter_node; | |
| while( not_final ) | |
| { | |
| if(current_node->next == NULL) | |
| {not_final = false;} | |
| else | |
| {current_node = current_node->next;} | |
| } | |
| return current_node; | |
| } | |
| /*Get_Last_Node_Function*/ | |
| Anodes Get_Last_Anode(Anodes &starter_node) | |
| { | |
| bool not_final = true; | |
| Anodes current_node = starter_node; | |
| while( not_final ) | |
| { | |
| if(current_node->next == NULL) | |
| {not_final = false;} | |
| else | |
| {current_node = current_node->next;} | |
| } | |
| return current_node; | |
| } | |
| /*Add_Node_Function*/ | |
| Cnodes Add_Node(Cnodes &starter_node, Cnodes new_node) | |
| { | |
| Cnodes Last_Node = Get_Last_Node(starter_node); | |
| Last_Node->next = new_node; | |
| //(struct object_arguments *)malloc(sizeof(object_arguments)) | |
| return Last_Node->next; | |
| } | |
| /*ReInit_Function*/ | |
| void ReInit(Cnodes &starter_node) | |
| { | |
| Cnodes current_node = starter_node; | |
| while(current_node != NULL) | |
| { | |
| if(current_node->arguments == NULL) | |
| { | |
| current_node->arguments = new struct object_arguments; | |
| current_node->arguments = Init_Head_Anode(); | |
| } | |
| current_node = current_node->next; | |
| } | |
| } | |
| /*Add_Anode_Function*/ | |
| Anodes Add_Anode(Anodes &starter_node, Anodes new_node) | |
| { | |
| Anodes Last_Node = Get_Last_Anode(starter_node); | |
| Last_Node->next = new_node; | |
| return Last_Node->next; | |
| } | |
| /*Generate_New_Anode_Function*/ | |
| Anodes Generate_New_Anode(int arg_id, int arg_type, int arg_length, std::string arg, std::string type, struct object_arguments* next) | |
| { | |
| Anodes new_anode = new struct object_arguments; | |
| new_anode->arg_id = arg_id; | |
| new_anode->arg_type = arg_type; | |
| new_anode->arg_length = arg_length; | |
| new_anode->arg = arg; | |
| new_anode->type = type; | |
| new_anode->next = next; | |
| return new_anode; | |
| } | |
| /*Generate_New_Node_Function*/ | |
| Cnodes Generate_New_Node(int id, string type, string flag, string dec_message, string enc_message, int base_params[5], struct connection_node* next) | |
| { | |
| Cnodes new_node = new struct connection_node; | |
| new_node->id=id; | |
| new_node->type=type; | |
| new_node->flag=flag; | |
| new_node->dec_message=dec_message; | |
| new_node->enc_message=enc_message; | |
| for(int i = 0; i<(sizeof(new_node->base_params)/sizeof(int));i++) | |
| { | |
| new_node->base_params[i]=base_params[i]; | |
| } | |
| new_node->next=next; | |
| return new_node; | |
| } | |
| /*Count_Nodes_Function*/ | |
| int Count_Nodes(Cnodes starter_node) | |
| { | |
| int count = 0; | |
| bool not_final = true; | |
| Cnodes current_node = starter_node; | |
| while( not_final ) | |
| { | |
| if(current_node->type != "head") | |
| {count++;} | |
| current_node = current_node->next; | |
| if(current_node == NULL) | |
| {not_final = false;} | |
| } | |
| return count; | |
| } | |
| /*Get_Node_By_Id_Function*/ | |
| //Returns node->type=="null" if node is not founded | |
| Cnodes Get_Node_By_Id( Cnodes &starter_node, int node_id ) | |
| { | |
| bool not_final = true; | |
| Cnodes current_node = starter_node; | |
| while( not_final ) | |
| { | |
| if(current_node->id == node_id) | |
| {not_final = false;} | |
| else if(current_node->next == NULL) | |
| {not_final = false;int test[5] = {1,2,3,4,5};current_node = Generate_New_Node(-1,"null","null","","",test,NULL);} | |
| else | |
| {current_node = current_node->next;} | |
| } | |
| return current_node; | |
| } | |
| /*Remove_Node_Function*/ | |
| void Remove_Node(Cnodes &starter_node, int node_id) | |
| { | |
| Cnodes prev_node = NULL; | |
| Cnodes current_node = starter_node->next; | |
| while( current_node != NULL ) | |
| { | |
| if(current_node->id == node_id) | |
| { | |
| if( (prev_node == NULL) || (prev_node->type == "head") ) | |
| { | |
| //cout << "first" <<current_node->id << endl; | |
| starter_node->next = current_node->next; | |
| delete current_node; | |
| } | |
| else if(current_node->next == NULL) | |
| { | |
| //cout << "second" <<current_node->id << endl; | |
| prev_node->next = NULL; | |
| delete current_node; | |
| } | |
| else | |
| { | |
| //cout << "third" <<current_node->id << endl; | |
| prev_node->next = prev_node->next->next; | |
| delete current_node; | |
| } | |
| break; | |
| } | |
| else | |
| { | |
| prev_node = current_node; | |
| current_node = current_node->next; | |
| } | |
| } | |
| } | |
| /*Print_Nodes_Function*/ | |
| void Print_Nodes(Cnodes starter_node) | |
| { | |
| Cnodes current_node = starter_node; | |
| cout << "Cnodes : "; | |
| while( current_node != NULL ) | |
| { | |
| cout << current_node->id <<","; | |
| current_node = current_node->next; | |
| } | |
| cout << endl; | |
| } | |
| /*Print_Anodes_Function*/ | |
| void Print_Anodes(Anodes starter_node) | |
| { | |
| Anodes current_node = starter_node; | |
| cout << "Anodes : "; | |
| while( current_node != NULL ) | |
| { | |
| cout << "{" << current_node->arg_id << ", " << current_node->arg_type << ", " << current_node->arg_length << "},"; | |
| current_node = current_node->next; | |
| } | |
| cout << endl; | |
| } | |
| /*Clear_Flags_Function*/ | |
| void Clear_Flags(Cnodes starter_node, Cnodes &starter_node_link, string frag) | |
| { | |
| Cnodes current_node = starter_node; | |
| while( current_node != NULL ) | |
| { | |
| if(current_node->flag == frag) | |
| {Remove_Node(starter_node_link,current_node->id);} | |
| current_node = current_node->next; | |
| } | |
| } | |
| /*CheckIfObjectExists_Function*/ | |
| bool CheckIfObjectExists(Cnodes starter_node, int node_id) | |
| { | |
| Cnodes current_node = starter_node; | |
| bool isset = false; | |
| while( current_node != NULL ) | |
| { | |
| if(current_node->id == node_id) | |
| {isset = true;break;} | |
| current_node = current_node->next; | |
| } | |
| return isset; | |
| } | |
| /**************************************WBDS_Functions*************************************/ | |
| /*DecodeMessageBaseParams_Function*/ | |
| //Decoding WBDS message to base params, it's in number systems, they have view like 8 + 8 + 4 + 8 + 8 + 4 without plus(+) | |
| //First 8 bytes describes length of all message, minus 8 symbols(self) | |
| //Message looks e.x. : 0000002000000000000D00000003002B27640000 | |
| void DecodeMessageBaseParams(std:: string dec_message, int * in_array) | |
| { | |
| char message_length[50]; | |
| char sequence_id[50]; | |
| char command_id[50]; | |
| char obj_type[50]; | |
| char obj_id[50]; | |
| char arg_count[50]; | |
| //decode 8 - 8,4,8,8,4 | |
| sscanf(StringToChar(dec_message), "%8s%8s%4s%8s%8s%4s",&message_length,&sequence_id,&command_id,&obj_type,&obj_id,&arg_count); | |
| //printf("%d %d %d %d %d %d",message_length,sequence_id,command_id,obj_type,obj_id,arg_count); | |
| int array[6] = {To10(message_length),To10(sequence_id),To10(command_id),To10(obj_type),To10(obj_id),To10(arg_count)}; | |
| in_array = array; | |
| } | |
| void TestFunc(int * array) | |
| { | |
| array[0] = 100500; | |
| } | |
| void UpdateMessageId(Cnodes &starter_node, int node_id, int message_new_id) | |
| { | |
| bool not_final = true; | |
| Cnodes current_node = starter_node; | |
| while( not_final ) | |
| { | |
| if(current_node->id == node_id) | |
| {not_final = false;current_node->base_params[4]=message_new_id;} | |
| else if(current_node->next == NULL) | |
| {not_final = false;} | |
| else | |
| {current_node = current_node->next;} | |
| } | |
| } | |
| void UpdateMessageDec(Cnodes &starter_node, int node_id, std::string new_dec) | |
| { | |
| bool not_final = true; | |
| Cnodes current_node = starter_node; | |
| while( not_final ) | |
| { | |
| if(current_node->id == node_id) | |
| {not_final = false;current_node->dec_message = new_dec;} | |
| else if(current_node->next == NULL) | |
| {not_final = false;} | |
| else | |
| {current_node = current_node->next;} | |
| } | |
| } | |
| bool IsMessageExists(Cnodes starter_node, int message_id) | |
| { | |
| bool not_final = true; | |
| bool result = false; | |
| Cnodes current_node = starter_node; | |
| while( not_final ) | |
| { | |
| if(current_node->base_params[4] == message_id) | |
| {not_final = false;result=true;} | |
| else if(current_node->next == NULL) | |
| {not_final = false;} | |
| else | |
| {current_node = current_node->next;} | |
| } | |
| return result; | |
| } | |
| Cnodes GetMessageById(Cnodes starter_node, int base_params_id) | |
| { | |
| bool not_final = true; | |
| Cnodes current_node = starter_node; | |
| while( not_final ) | |
| { | |
| if(current_node->base_params[4] == base_params_id) | |
| {not_final = false;} | |
| else if(current_node->next == NULL) | |
| {not_final = false;int test[5] = {1,2,3,4,5};current_node = Generate_New_Node(-1,"null","null","","",test,NULL);} | |
| else | |
| {current_node = current_node->next;} | |
| } | |
| return current_node; | |
| } | |
| /*GetChannelName_Function*/ | |
| //Channel name by it's id | |
| std::string GetChannelName(int channel_id) | |
| { | |
| switch(channel_id) | |
| { | |
| case 1: | |
| return "OBJTYPE_MSG"; | |
| break; | |
| case 2: | |
| return "OBJTYPE_AREA"; | |
| break; | |
| case 3: | |
| return "OBJTYPE_USER"; | |
| break; | |
| case 4: | |
| return "OBJTYPE_CLAN"; | |
| break; | |
| case 5: | |
| return "OBJTYPE_TRADE"; | |
| break; | |
| case 6: | |
| return "OBJTYPE_PARTY"; | |
| break; | |
| case 7: | |
| return "OBJTYPE_RAID"; | |
| break; | |
| case 10: | |
| return "OBJTYPE_AUX"; | |
| break; | |
| } | |
| } | |
| /*GetObjectArguments_Function*/ | |
| //Decodes message arguments into nodes aka Array | |
| Anodes GetObjectArguments(Cnodes chain) | |
| { | |
| Anodes anode_starter = new struct object_arguments; | |
| anode_starter = Init_Head_Anode(); | |
| int aknod = 0; | |
| int arguments_count = chain->base_params[5]; | |
| std::string buffer = ""; | |
| int start = 40; | |
| int arg_id, arg_type, arg_length; | |
| std::string arg = ""; | |
| std::string message = chain->dec_message; | |
| while(arguments_count > 0) | |
| { | |
| //do it :) | |
| //reading argument id | |
| for (int i = start; i < (start+8); i++) { buffer += message[i]; }//8 | |
| arg_id = To10(buffer); | |
| start += 8; | |
| buffer = ""; | |
| //reading argument type | |
| for (int i = start; i < (start + 4); i++) { buffer += message[i]; }//4 | |
| arg_type = To10(buffer); | |
| start += 4; | |
| buffer = ""; | |
| //reading argument length | |
| for (int i = start; i < (start + 8); i++) { buffer += message[i]; }//8 | |
| arg_length = To10(buffer); | |
| start += 8; | |
| buffer = ""; | |
| //reading argument content | |
| for (int i = start; i < (start + arg_length); i++) { buffer += message[i]; }//arg_length | |
| arg = buffer; | |
| start += arg_length; | |
| buffer = ""; | |
| //goto | |
| arguments_count -= 1; | |
| if( aknod == 0 ) | |
| { | |
| anode_starter->next = Generate_New_Anode(arg_id,arg_type,arg_length,arg,"chain",NULL); | |
| } | |
| else | |
| { | |
| Anodes current_anode = anode_starter; | |
| for( int i = 0; i < aknod; i++ ) | |
| { | |
| current_anode = current_anode->next; | |
| } | |
| current_anode->next = Generate_New_Anode(arg_id,arg_type,arg_length,arg,"chain",NULL); | |
| } | |
| aknod++; | |
| } | |
| return anode_starter; | |
| } | |
| /*MountString_Function*/ | |
| std::string MountString( Cnodes cchain, Anodes achain ) | |
| { | |
| //Encode base params | |
| std::string sequenceId = FillString( To16( cchain->base_params[1] ), "0", 8, true ); | |
| std::string commandId = FillString( To16( cchain->base_params[2] ), "0", 4, true ); | |
| std::string objType = FillString( To16( cchain->base_params[3] ), "0", 8, true ); | |
| std::string objId = FillString( To16( cchain->base_params[4] ), "0", 8, true ); | |
| std::string argCount = FillString( To16( cchain->base_params[5] ), "0", 4, true ); | |
| //Encode arguments list | |
| std::string args_string = ""; | |
| if( cchain->base_params[5] > 0 ) | |
| { | |
| Anodes carg = achain->next; | |
| bool not_final = true; | |
| while(not_final) | |
| { | |
| args_string += FillString( To16( carg->arg_id ), "0", 8, true ); | |
| args_string += FillString( To16( carg->arg_type ), "0", 4, true ); | |
| args_string += FillString( To16( carg->arg_length ), "0", 8, true ); | |
| args_string += carg->arg; | |
| if(carg->next == NULL) | |
| not_final = false; | |
| else | |
| carg = carg->next; | |
| } | |
| } | |
| //length | |
| std::string msg_resulted = sequenceId + commandId + objType + objId + argCount + args_string; | |
| std::string messageLength = FillString( To16( msg_resulted.length() ), "0", 8, true ); | |
| //Implode all parts | |
| std::string result = messageLength + sequenceId + commandId + objType + objId + argCount + args_string; | |
| //Return result | |
| return result; | |
| } | |
| Cnodes Node_Starter = new struct connection_node; | |
| int main() { | |
| Node_Starter = Init_Head_Node(); | |
| int test[5] = {1,2,3,4,5}; | |
| Add_Node(Node_Starter,Generate_New_Node(cid++,"message","in_process","","",test,NULL)); | |
| Add_Node(Node_Starter,Generate_New_Node(cid++,"message","in_process",FileGetContents("E:\\cpp_daemon\\test_message.txt"),"",test,NULL)); | |
| Remove_Node(Node_Starter,1); | |
| Clear_Flags(Node_Starter,Node_Starter,"off_process"); | |
| cout << "Last_Node_Type : " << Get_Last_Node(Node_Starter)->type << endl; | |
| cout << "Nodes Count : " << Count_Nodes(Node_Starter) << endl; | |
| int search_node = 4; | |
| Cnodes search_result = Get_Node_By_Id(Node_Starter,search_node); | |
| if(search_result->type == "null") | |
| {cout << "Node id " << search_node << " not found" << endl;} | |
| else | |
| { | |
| cout << "Node id " << search_node << " found, type is " << search_result->type << endl; | |
| } | |
| Print_Nodes(Node_Starter); | |
| cout << endl << "---" << endl; | |
| int arrayz[6] = {0,0,0,0,0,0}; | |
| DecodeMessageBaseParams(Node_Starter->next->enc_message,arrayz); | |
| if(CheckIfObjectExists(Node_Starter,2)) | |
| cout << "ISSET" << endl; | |
| else | |
| cout << "NOT ISSET" << endl; | |
| //UpdateMessageEnc(Node_Starter,Node_Starter->next->id,"zbeta"); | |
| cout << Node_Starter->next->enc_message << endl; | |
| Print_Array(Node_Starter->next->base_params); | |
| cout << GetChannelName(3) << endl; | |
| cout << endl << "---" << endl; | |
| cout << FillString("test","0",5,false) << endl; | |
| //cout << MountString( Get_Node_By_Id(Node_Starter,2), GetObjectArguments( Get_Node_By_Id(Node_Starter,2) ) ) << endl; | |
| cout << To16(255) << endl; | |
| if(IsMessageExists(Node_Starter,4)) | |
| cout << "Exists" << endl; | |
| else | |
| cout << "Not Exists" << endl; | |
| int tester = 100; | |
| char testers[10] = ""; | |
| sprintf(testers,"%d",tester); | |
| cout << " ::: " << testers << endl; | |
| const int TEST_CONST = 0xFF; | |
| cout << TEST_CONST << endl; | |
| std::string ztest = "100500"; | |
| int varz; | |
| varz = atoi(ztest.c_str()); | |
| cout << ":::->" <<varz <<" / " <<ztest.size() <<endl; | |
| int arrayzt[1] = {-100}; | |
| TestFunc(arrayzt); | |
| cout << arrayzt[0]; | |
| } |
прикольно. никто не писал комментариев.
Работает как швейцарские часы?
уже нашли откуда был спизжен код?
Интересно
в 19 вижуалке ругается что то
Пиздец, такого дерьма я ещё никогда не видел.
0/10
в 19 вижуалке ругается что то
Говнокод потому что.
настоящий профессионал!... в сфере говнокодинга
настоящий профессионал!... в сфере говнокодинга
Согласен
лютый гавнокод, чел, удали себя
уже нашли откуда был спизжен код?
такой гавнокод мог написать только он и дударь, выбора немного
класный код
в 19 вижуалке ругается что то
Код компилился под Linux через GCC.
Сам файлик из архива - датируется 2017 годом.
Написан был после недели изучения C++.
Реализует серверную часть чата на клиенте.
Код не полный, писался на скорую руку - типа proof of concept.
О, и да - семафоры это круто :3
/*MountString_Function*/
std::string MountString( Cnodes cchain, Anodes achain )
спасибо за комментарии
Одобряю, хороший код. Хауди, сделаешь код-ревью моих проектов на С++?
Хауди Хо, разлогинься
Сам файлик из архива - датируется 2017 годом.
Написан был после недели изучения C++.
Реализует серверную часть чата на клиенте.
Код не полный, писался на скорую руку - типа proof of concept.
О, и да - семафоры это круто :3
ты за неделю не мог выучить хотя бы основы ООП? Ладно бы ты был новичком и с++ был бы твоим первый ЯП, но у тебя был большой опыт до этого чел.... За неделю ты мог бы выучить больше. И уж точно не гавнокодить так
😭😭😭
Ну тут и токсики. Каждый имеет право на не очень хороший код. Главное, чтобы он не вредил
позитивно
Посмотрите на мой говнокод у меня в профиле, поставьте звездочек, позязя :З
Ахахахаха, даже у меня не настолько говнокод
Проснувшись утром я почувствовал мимолетный, но уже довольно едкий запашок. Полусонный мозг подсказывал мне, что это запах экскрементов. Греша на кота я методично начал осматривать комнату в поисках источника запаха. Каковым же было мое удивление, когда я увидел коричневые разводы на своем мониторе. Пиксели буквально источали невыносимый смрад. Я протер монитор, а под толстым зловонным слоем оказался 100% ЛУЧШИЙ КОД ОТ ПРОФЕССИОНАЛА С++. Коту дал ментального пинка, а Хауди Хо - лучший ютубер ever.
Мне сегодня снилось, что я водитель автобуса программирования, почти все программисты были пассажирами, все выглядели как бомжи, грязные, в рваной одежде. Запомнил только Хауди, он был полуголым, в грязи, бегал по салону и визжал какую-то хуйню, я его ударил пару раз
Oh my gi