/*
GGadu czyli obsluga GG na serwerze z AMXX
by ME|-=PAVULON=-[PL]
http://www.mutant-enemy.pl/profile.php?mode=viewprofile&u=7
http://amxx.pl/profile.php?mode=viewprofile&u=1738

Oficjalna strona projektu: http://amxx.pl/viewtopic.php?t=10507

Cvary:
amx_gg_numer 1 - numer gg z ktorego maja byc wysylane wiadomosci
amx_gg_haslo "x" - haslo do tego numeru gg
amx_gg_opis_on "Super Serwer z obsluga GG" - opis serwera kiedy wlaczony
amx_gg_opis_off "Super Serwer z obsluga GG" - opis kiedy wylaczony
amx_gg_opis_add 3 - czy dodawac do opisu dodatkowe informacje ? 
		1 = do opisu ON (+) 2 = do opisu OFF / 3 = do obu
amx_gg_log 6 - co logowac ? 1 = wiadomosci przychodzace (+) 2 = rozmowy (+) 4 = funkcje
amx_gg_losowy_serw 0 - uzywac losowego serwera gg do logowania ?
		1 - tylko jesli notoperating, 2 - przy nieudanym pobraniu ip
amx_gg_dzwiek 1 - dzwiek przy otrzymaniu wiadomosci ? 1 = tak, 0 = nie
		
Komendy:
say(_team) gg: nr_gg wiadomosc - wysyla wiadomosc na dany numer gg
amx_gg_admins_reload - pobiera ponownie dane adminow z pliku (ADMIN_BAN)
amx_gg_bans_reload - pobiera ponownie bany z pliku (ADMIN_BAN)
amx_gg_servers_reload - pobiera ponownie serwery z pliku (ADMIN_BAN)

Changelog:
0.3.4 - pobieranie serwerow z pliku
0.3.3 - pobrawione bledy i stabilnosc
0.3.2 - banowanie numerow gg + autobanowanie za flood
0.3.1 - komenda rcon: dla adminow ADMIN_RCON wykonuje komende bez sprawdzania innych flag itp
0.3 - administracja serwerem z poziomu gg - komenda admin:
0.2 - rozmowy: chat <-> gg
0.1.2 - rozwiazane pewne problemy + logowanie wiadomosci (cvar)
0.1.1 - poprawiony odbior duzych (ilosci) wiadomosci
0.1 - pierwsze wydanie, wstepne opanowanie protokolu gg + podstawowe funkcje

Informacje niezbedne do storzenia pluginu zdobylem po czesci na stronie: http://toxygen.net/libgadu/protocol/
a w duzej mierze jednak dzieki wlasnej pracy i programom: Gadu-Gadu 7.7.0, Wireshark 1.0.5, Notepad2 3.0.20, HexAssistant 2.5, Kalkulator 5.1 
oraz oczywiscie HLDS + CS 1.6


Serwery na których jest full support GGadu:
87.98.235.151:27015 [PL] Sysunia I TP [ www.mutant-enemy.pl ] GG:10323592
87.98.235.152:27015 [PL] Sysunia II TP [ www.mutant-enemy.pl ] GG:10326286
87.98.235.153:27015 [PL] Sysunia III TP [ www.mutant-enemy.pl ] GG:10332410
94.23.94.230:27015 [PL] Sysunia IV TP [ www.mutant-enemy.pl ] GG:10338046
*/

#include <amxmodx>
#include <amxmisc>
#include <sockets>

#define PLUGIN "GGadu"
#define VERSION "0.3.4_x2"
#define AUTHOR "Pavulon"

#pragma dynamic 65536

#define MAXLEN 480 //maksymalna dlugosc pakietu wysylanego
#define MAXPACKET 1460 //maksymalna dlugosc pakietu z serwera GG

new numer_gg, haslo_gg, opis_gg_on, opis_gg_off, opis_gg_add, losowy_gg //cvary

new sock, serwer_ip[16], serwer_port, connected, errors, conn_tries //troche podstawowych danych serwera

public plugin_init()
{
	register_plugin(PLUGIN, VERSION, AUTHOR)
	numer_gg = register_cvar("amx_gg_numer", "1")
	haslo_gg = register_cvar("amx_gg_haslo", "x")
	opis_gg_on = register_cvar("amx_gg_opis_on", "Super Serwer z obsluga GG")
	opis_gg_off = register_cvar("amx_gg_opis_off", "Super Serwer z obsluga GG")
	opis_gg_add = register_cvar("amx_gg_opis_add", "3")
	losowy_gg = register_cvar("amx_gg_losowy_serw", "0")
	
	set_task(0.5, "logon", 1) //logowanie do serwera
	set_task(5.0, "check", 3, _, _, "b") //sprawdzanie dostepnosci nowych wiadomosci
	set_task(60.0, "ping", 2, _, _, "b") //ping
}

/*przygotowanie folderu oraz niezbednych plikow*/
public plugin_cfg()
{
	new basedir[64], file_cfg[64]
	get_basedir(basedir, 63)
	
	formatex(file_cfg, 63, "%s/GGadu/GGadu.cfg", basedir)
	
	if (file_exists (file_cfg))
		server_cmd("exec %s", file_cfg)
	else
		write_file (file_cfg, ";[GGadu] Umiesc w tym pliku cvar'y do plugin'a.") 
}

/*sprawdzamy czy jest zmiana socket'cie, jesli tak to pobieramy i dajemy dalej*/
public check()
{
	if (connected)
	{
		if (socket_change(sock))
		{
			new buffer[MAXPACKET+1]
			socket_recv(sock, buffer, MAXPACKET)
			
			sprawdz_wiadomosc(buffer)
		}
	}
}

/*trzeba sprawdzic co to dostalismy*/
public sprawdz_wiadomosc(buff[])
{
	new tmp[5]
	
	/*pierwsze 4 bity okreslaja rodzaj pakietu*/
	wytnij_text(buff, tmp, 0, 3, sizeof(tmp), 1, 0)
	new typ = hex2dec(tmp)
	switch (typ)
	{
		case 11: //serwer nas rozlacza wiec laczymy sie ponownie
		{
			rozlacz()
			logon()
		}
		
		case 0: //jakies dziwne pakiety - za duzo blednych wiec reconnect
		{
			errors++
			if (errors > 4)
			{
				rozlacz()
				logon()	
			}
		}
		
		default: //nie znany pakiet
			errors = 0
	}
	return PLUGIN_CONTINUE
}


/*logujemy sie do gg i ustawiamy opis*/
public logon()
{
	new gg_numer = get_pcvar_num(numer_gg)
	new gg_haslo[36], gg_opis_on[71]
	get_pcvar_string(haslo_gg, gg_haslo, 35)
	get_pcvar_string(opis_gg_on, gg_opis_on, 71)
	
	if (get_pcvar_num(opis_gg_add)&1<<0)
	{
		new mapname[32]
		get_mapname(mapname,31)
		format(gg_opis_on, 70, "%s [ON] @ map %s",gg_opis_on, mapname)
	}
	
	if (!szukaj_serwera(numer_gg)) //brak danych IP:PORT wiec nie wiadomo z czym polaczyc
	{
		set_task(++conn_tries*30.0, "logon")
		log_amx("[GGadu] Nie znaleziono serwera. Proba ponownego polaczenia za %d sekund", conn_tries*30)
		return PLUGIN_HANDLED
	}
	
	new klucz = pobierz_klucz()
	new hash = oblicz_hash(gg_haslo, klucz)

	if (!loguj(gg_numer, hash)) //zle haslo lub serwer niedostepny
	{
		set_task(++conn_tries*30.0, "logon")
		log_amx("[GGadu] Nieudane polaczenie. Proba ponownego polaczenia za %d sekund", conn_tries*30)
		return PLUGIN_HANDLED
	}
	
	connected = 1
	conn_tries = 0
	zmien_status(gg_opis_on, 1)
	wyslij_nr_listy(1) //dla pewnosci ze bedzie opis
	log_amx("[GGadu] Polaczono z %s:%d", serwer_ip, serwer_port)
	
	return PLUGIN_CONTINUE
}

/*wylogowujemy sie i ustawiamy opis na koniec*/
public logoff()
{
	new gg_opis_off[71]
	get_pcvar_string(opis_gg_off, gg_opis_off, 71)
	
	if (get_pcvar_num(opis_gg_add)&1<<1)
	{
		format(gg_opis_off, 70, "%s [OFF] mapchange ?",gg_opis_off)
	}
	zmien_status(gg_opis_off, 0)
	wyloguj()
	connected = 0
}

/*szukamy adresu ip i portu serwera gg*/
public szukaj_serwera(numer)
{
	new error, prep_msg[256], buffer[128]
	new ips[23] = {2,4,5,6,7,12,13,14,17,18,19,20,21,24,25,26,27,28,29,31,32,33}

	new socket = socket_open("appmsg.gadu-gadu.pl", 80, SOCKET_TCP, error)
	if (socket<1) //nieudane polaczenie
	{
		switch(error)
		{
			case 1: server_print("[GGadu] Error creating socket")
			case 2: server_print("[GGadu] Could not resolve hostname")
			case 3: server_print("[GGadu] Couldn't connect to given hostname:port") //moze pojawic sie drobny lagg
		}
		
		if (get_pcvar_num(losowy_gg)==2) //probujemy z serwerem losowym ?
		{
			format(serwer_ip, 15, "91.197.13.%d", ips[random_num(0,22)])
			serwer_port = 8047
			log_amx("[GGadu] Laczenie z losowym serwerem %s:%d", serwer_ip, serwer_port)
			return 1
		}
		return 0
	}
	
	/*"prosimy" o podanie odpowiedniego IP i portu*/
	new len = format(prep_msg, 1023, "GET /appsvc/appmsg.asp?fmnumber=%d HTTP/1.0^r^n", numer)
	len += format(prep_msg[len], 1023-len, "Host: appmsg.gadu-gadu.pl^r^n^r^n")
	
	socket_send(socket, prep_msg, strlen(prep_msg))
	socket_recv(socket, buffer, 127)
	socket_close(socket)
	
	if (contain(buffer, "200 OK")==-1) //nie dostalismy pozytywej odpowiedzi od serwera wiec IP brak
	{
		if (get_pcvar_num(losowy_gg)==2)
		{
			format(serwer_ip, 15, "91.197.13.%d", ips[random_num(0,22)])
			serwer_port = 8047
			log_amx("[GGadu] Laczenie z losowym serwerem %s:%d", serwer_ip, serwer_port)
			return 1
		}
		return 0
	}
	
	/*dzielimy odpowiedz i wyciagamy interesujace nas dane*/
	new tmp[2]
	split(buffer, tmp, 1, buffer, 127, "^r^n^r^n")
	strtok(buffer, tmp, 1, buffer , 127, ' ')
	strtok(buffer, tmp, 1, buffer , 127, ' ')
	strtok(buffer, tmp, 1, buffer , 127, ' ')
	strtok(buffer, buffer, 21, tmp , 1, ' ')
	strtok(buffer, serwer_ip, 15, buffer , 5, ':')
	serwer_port = str_to_num(buffer)
	
	if (strlen(serwer_ip) == 0 || strlen(buffer) == 0) //znow cos nie tak, zmienil sie uklad lub nie na taka odpowiedz liczylismy
	{
		if (get_pcvar_num(losowy_gg)==2)
		{
			format(serwer_ip, 15, "91.197.13.%d", ips[random_num(0,22)])
			serwer_port = 8047
			log_amx("[GGadu] Laczenie z losowym serwerem %s:%d", serwer_ip, serwer_port)
			return 1
		}
		return 0
	}
	
	if (equal(serwer_ip, "notoperating") && get_pcvar_num(losowy_gg)) //serwer sie buntuje
	{
		format(serwer_ip, 15, "91.197.13.%d", ips[random_num(0,22)])
		serwer_port = 8047
		log_amx("[GGadu] Laczenie z losowym serwerem %s:%d", serwer_ip, serwer_port)
	}
	return 1
}

/*czas pobrac klucz zeby bylo z czego generowac hash*/
public pobierz_klucz()
{
	new buffer[25], klucz[5], error
	sock = socket_open(serwer_ip, serwer_port, SOCKET_TCP, error)
	socket_recv(sock, buffer, 24)
	
	wytnij_text(buffer, klucz, 8, 11, sizeof(klucz), 1, 0)
	
	return hex2dec(klucz)	
}

/*wysylamy pakiet logowania*/
public loguj(numer, hash)
{
	new respon[12], prep_msg[256]
	formatex(prep_msg, 255, "%s%s%s%s"
	, "1500000020000000"
	, dec2hex(numer, 1)
	, dec2hex(hash, 1)
	, "2000000022000000000000000000000000000000000014BE")
	
	socket_send2(sock, str2hex(prep_msg,0,""), strlen(prep_msg)/2)
	
	socket_recv(sock, respon,11)
	
	if (respon[0] == 3) return 1
	
	return 0
}

/*niezbedny pakiet jak chcemy wyslac wiadomosc i przydaje sie przy zmianie statusu*/
public wyslij_nr_listy(adresat)
{
	new prep_msg[30]
	formatex(prep_msg, 29, "%s%s%s"
	, "1000000005000000"
	, dec2hex(adresat, 1)
	, "03")
	socket_send2(sock, str2hex(prep_msg, 0, ""), strlen(prep_msg)/2)
}

/*ping zeby serwer o nas nie zapomnial*/
public ping()
{
	if (connected) 
		socket_send2(sock, str2hex("0800000000000000",0,""), 8)
}

/* zmieniamy opis i stan: dostepny/niedostepny*/
public zmien_status(opis[], on)
{
	new prep_msg[256]
	formatex(prep_msg, 255, "%s%s%s"
	, "02000000"
	, dec2hex(4 + strlen(opis), 1)
	, dec2hex((on)?0x0004:0x0015, 1))
	
	socket_send2(sock, str2hex(prep_msg,1,opis), strlen(prep_msg)/2+strlen(opis))
}

/*wylogowujemy sie z gg*/
public wyloguj()
{
	socket_send2(sock, str2hex("020000002000000001000000",0,""), 12)
	
	set_task(1.0, "rozlacz")
}

/*i zamykamy polaczenie*/
public rozlacz()
{
	log_amx("[GGadu] Polaczenie zerwane")
	socket_close(sock)
	connected = 0
}

/*zmiana mapy lub serwer going down wiec konczymy z gg*/
public plugin_end()
{
	logoff()
}

/*obliczamy hash z naszego hasla i pobranego klucza*/
public oblicz_hash(haslo[], klucz)
{
	new x0=0, x1=0, y0=0, y1=0, z=0, tmp=0;
	y0 = (klucz << 16) >> 16; y1 = klucz >> 16 ;
	new len = strlen(haslo)
	for (new i=0; i<len; i++)
	{
		x0 = (x0 & 0xFF00) | haslo[i]; x1 &= 0xFFFF;
		y0 ^= x0; y1 ^= x1;
		y0 += x0; y1 += x1;
		x1 <<= 8; x1 |= (x0 >> 8); x0 <<= 8;
		y0 ^= x0; y1 ^= x1;
		x1 <<= 8; x1 |= (x0 >> 8); x0 <<= 8;
		y0 -= x0; y1 -= x1;
		x1 <<= 8; x1 |= (x0 >> 8); x0 <<= 8;
		y0 ^= x0; y1 ^= x1;
		z = y0 & 0x1F;
		
		y0 &= 0xFFFF; y1 &= 0xFFFF;
		if (z <= 16)
		{
			tmp= (y1 << z) | (y0 >> (16-z));
			y0 = (y1 >> (16-z)) | (y0 << z);
			y1 = tmp;    
			} else {
			tmp= y0 << (z-16);
			y0 = (y0 >> (32-z)) | ( ((y1 << z) >> z) << (z-16) );
			y1 = (y1 >> (32-z)) | tmp;
		}
		y0 &= 0xFFFF; y1 &= 0xFFFF;
	}
	
	tmp=y0, z=0
	while (z<16)
	{
		if (y1&(1<<z))
		{
			tmp += 1<<z+16
		}
		z++
	}
	return tmp
}

/*zamiana 8b hax -> dec*/
public hex2dec(hex[5])
{
	new wynik, tmp
	for(new i = 0; i < 4; i++)
	{
		tmp = hex[i]
		if (tmp<0) tmp+=256
		
		wynik += tmp*power(256, 3-i)
	}
	return wynik
}

/*zamiana liczby na hex z uwzglednieniem kirunku zapisu: little/big endian*/
public dec2hex(dec_num, litle)
{
	new wynik_bin[34], wynik_hex[9],wynik_hexl[9], i,j, num, tmp
	num = dec_num
	
	while (i<32) wynik_bin[31-i] = (num&(1<<i++))?1:0
	
	for (i=0; i<8; i++)
	{
		tmp = 0
		for (j=3; j>=0; j--)
		{
			tmp += wynik_bin[4*i+(3-j)]*1<<j
		}
		
		if (!litle) tmp += tmp<10 ? 48 : 55
		wynik_hex[i] = tmp
	}
	
	if (litle)
	{
		new w1, w2
		for (i=0; i<4; i++)
		{
			for (j=1; j>=0; j--)
			{
				w1 = 2*i+1-j
				w2 = 7-j-2*i
				wynik_hexl[w1] = wynik_hex[w2]
				wynik_hexl[w1] += wynik_hexl[w1]<10 ? 48 : 55
			}
		}
		return wynik_hexl
	}
	
	return wynik_hex
}

/*"tekst" na "hex". podstawa pakietow*/
public str2hex(str[], param, tresc[])
{
	new len, wynik[MAXLEN*2],i, tmp
	len = strlen(str)
	while (i<len)
	{
		tmp = str[i]
		tmp -= (str[i]>57)?55:48
		tmp *= 16
		tmp += str[i+1]
		tmp -= (str[i+1]>57)?55:48
		wynik[i/2]=tmp
		i += 2
	}
	if (param)
	{
		len /= 2
		new len2 = strlen(tresc)
		for (i=0; i<len2; i++)
			wynik[len+i] = tresc[i]
		
		if (param == 2) wynik[len+i]=0
	} 
	
	return wynik
}

/*wycinamy kawalek tesktu z drugiego z kilkoma parametrami typu kierunek zapisu i czy '0' na koncu*/
public wytnij_text(skad[], dokad[], poczatek, koniec, maxlen, prawolewo, zero)
{
	new i, len = koniec-poczatek
	if (len>maxlen) len = maxlen-1
	if (prawolewo)
	{
		for (i=0; i<=len; i++)
		{
			dokad[i] = skad[koniec-i]
		}
	} else
	{
		for (i=0; i<=len; i++)
		{
			dokad[i] = skad[poczatek+i]
		}
	}
	if (zero)
		dokad[++i]=0
}
/* AMXX-Studio Notes - DO NOT MODIFY BELOW HERE
*{\\ rtf1\\ ansi\\ deff0{\\ fonttbl{\\ f0\\ fnil Tahoma;}}\n\\ viewkind4\\ uc1\\ pard\\ lang1045\\ f0\\ fs16 \n\\ par }
*/
