Netcode 1.50
Généralités
Le protocole réseau utilisé par T4C est du type non-connecté. Ce sont des datagrammes UDP. Les données de ce datagramme comprennent un en-tête sur 12 octets, éventuellement suivis d'une clé de cryptage, d'une chaîne de données cryptées (le pak), ainsi que d'une somme de contrôle.
Le cryptage est une série mathématique qui porte sur les octets 16 du datagramme et suivants, dont la clé est donnée à l'octet 12 sur 4 octets.
Le checksum (somme de contrôle) est donné sur les 2 derniers octets du datagramme et porte sur les octets 16 du datagramme et suivants, avant encryption. Ce checksum est par conséquent masqué par l'encryption.
Les datagrammes de taille supérieure à 1024 octets sont systématiquement fragmentés (c'est-à-dire, envoyés en plusieurs parties de sorte que chaque partie envoyée ne fasse pas plus de 1024 octets).
Schéma canonique d'un datagramme du protocole 1.50 :
[HEADER][seed][DATA.........................[checksum]]
[............partie encryptée...........]
ATTENTION: toutes les valeurs de l'en-tête, la clé de cryptage et la somme de contrôle sont en LITTLE ENDIAN (format de représentation des types de données Intel), mais les données du pak sont en BIG ENDIAN (format de représentation des types de données données Motorola). En lisant les données du pak sur un ordinateur de type x86, le programmeur doit penser à INVERSER l'ordre des octets. Les chaînes de caractères, étant considérées comme des tableaux d'octets, ne sont pas affectées.
Méthode de cryptage
La méthode de cryptage a dramatiquement été changée dans cette version comparativement aux versions précédentes. Ici, on utilise le générateur de nombres pseudo-aléatoires du système, qui est en fait l'expression d'une série mathématique, pour renseigner un tableau de valeurs de masquage. Ensuite, selon ces valeurs, les données sont masquées et éventuellement décalées demi-octet par demi-octet suivant une expression conditionnelle à 21 états. Il s'agit, bien entendu, d'un algorithme réversible.
A la fois les données du pak et le footer (checksum) sont cryptés, c'est-à-dire tout ce qui se situe au-delà du header et de la clé de cryptage.
signed short Pak_SwitchEncryptionOff_150 (pak_t *pak, unsigned long seed)
{
// the 1.50 protocol cryptography uses extensively the standard C random number generator,
// which is a VERY BAD idea, since its implementation may differ from system to system !!!
char stack1[10];
char stack2[10];
unsigned char a;
unsigned char c;
char *edi;
char *ebp;
int index;
unsigned int algo;
signed short checksum;
// initialize the system's pseudo-random number generator from the seed given in the datagram
// (they apparently swapped the bytes in an attempt to confuse the reverse-engineerers)
srand ( (int) (((unsigned char *) &seed)[0] << 24)
| (int) (((unsigned char *) &seed)[3] << 16)
| (int) (((unsigned char *) &seed)[1] << 8)
| (int) (((unsigned char *) &seed)[2]));
// now generate the crypto tables for the given datagram length
// stack sequences
for (index = 0; index < 10; index++)
{
stack1[index] = (char) rand ();
stack2[index] = (char) rand ();
}
// xor table
for (index = 0; index < pak->data_size; index++)
{
cryptotables_150.xor[index] = (unsigned char) stack2[rand () % 10];
cryptotables_150.xor[index] *= (unsigned char) stack1[rand () % 10];
cryptotables_150.xor[index] += rand ();
}
// offset & algo tables
for (index = 0; index < pak->data_size; index++)
{
cryptotables_150.offsets[index] = rand () % pak->data_size;
if (cryptotables_150.offsets[index] == (unsigned int) index)
cryptotables_150.offsets[index] = (index == 0 ? 1 : 0);
cryptotables_150.algo[index] = rand () % 21;
}
// cryptographic tables are generated, now apply the algorithm
for (index = pak->data_size - 1; index >= 0; index--)
{
algo = cryptotables_150.algo[index];
ebp = &pak->data[cryptotables_150.offsets[index]];
edi = &pak->data[index];
a = *ebp;
c = *edi;
if (algo == 0) { *edi = ((a ^ c) & 0x0F) ^ c; *ebp = ((a ^ c) & 0x0F) ^ a; }
else if (algo == 1) { *edi = ((a ^ c) & 0x0F) ^ c; *ebp = (a >> 4) | (c << 4); }
else if (algo == 2) { *edi = (c >> 4) | (c << 4); *ebp = (a >> 4) | (a << 4); }
else if (algo == 3) { *edi = (a >> 4) | (c << 4); *ebp = ((a ^ c) & 0x0F) ^ c; }
else if (algo == 4) { *edi = (a & 0x0F) | (c << 4); *ebp = (a & 0xF0) | (c >> 4); }
else if (algo == 5) { *edi = (c & 0xF0) | (a >> 4); *ebp = (a << 4) | (c & 0x0F); }
else if (algo == 6) { *edi = (a >> 4) | (c << 4); *ebp = (a << 4) | (c >> 4); }
else if (algo == 7) { *edi = (c & 0xF0) | (a >> 4); *ebp = (a & 0x0F) | (c << 4); }
else if (algo == 8) { *edi = (a & 0x0F) | (c << 4); *ebp = (c & 0xF0) | (a >> 4); }
else if (algo == 9) { *edi = (a & 0xF0) | (c >> 4); *ebp = (a & 0x0F) | (c << 4); }
else if (algo == 10) { *edi = (a << 4) | (c & 0x0F); *ebp = (a & 0xF0) | (c >> 4); }
else if (algo == 11) { *edi = (a << 4) | (c >> 4); *ebp = ((a ^ c) & 0x0F) ^ a; }
else if (algo == 12) { *edi = (a >> 4) | (a << 4); *ebp = (c >> 4) | (c << 4); }
else if (algo == 13) { *edi = a; *ebp = c; }
else if (algo == 14) { *edi = (a & 0xF0) | (c >> 4); *ebp = (a << 4) | (c & 0x0F); }
else if (algo == 15) { *edi = ((a ^ c) & 0x0F) ^ a; *ebp = ((a ^ c) & 0x0F) ^ c; }
else if (algo == 16) { *edi = a; *ebp = (c >> 4) | (c << 4); }
else if (algo == 17) { *edi = (a << 4) | (c & 0x0F); *ebp = (c & 0xF0) | (a >> 4); }
else if (algo == 18) { *edi = (a << 4) | (c >> 4); *ebp = (a >> 4) | (c << 4); }
else if (algo == 19) { *edi = (a >> 4) | (a << 4); *ebp = c; }
else if (algo == 20) { *edi = ((a ^ c) & 0x0F) ^ a; *ebp = (a << 4) | (c >> 4); }
}
// and finally, quadruple-XOR the data out
for (index = pak->data_size - 1; index >= 0; index--)
{
if (index <= pak->data_size - 4)
{
pak->data[index + 0] ^= (cryptotables_150.xor[index] & 0x000000FF); // we can XOR 4 bytes in a row
pak->data[index + 1] ^= (cryptotables_150.xor[index] & 0x0000FF00) >> 8;
pak->data[index + 2] ^= (cryptotables_150.xor[index] & 0x00FF0000) >> 16;
pak->data[index + 3] ^= (cryptotables_150.xor[index] & 0xFF000000) >> 24;
}
else if (index == pak->data_size - 3)
{
pak->data[index + 0] ^= (cryptotables_150.xor[index] & 0x0000FF); // we can XOR 3 bytes in a row
pak->data[index + 1] ^= (cryptotables_150.xor[index] & 0x00FF00) >> 8;
pak->data[index + 2] ^= (cryptotables_150.xor[index] & 0xFF0000) >> 16;
}
else if (index == pak->data_size - 2)
{
pak->data[index + 0] ^= (cryptotables_150.xor[index] & 0x00FF); // we can XOR 2 bytes in a row
pak->data[index + 1] ^= (cryptotables_150.xor[index] & 0xFF00) >> 8;
}
else if (index == pak->data_size - 1)
pak->data[index] ^= (cryptotables_150.xor[index] & 0xFF); // end of stream
}
// in the 1.50 protocol, the checksum info is at the trailing end of the pak.
checksum = *(unsigned short *) &pak->data[pak->data_size - 2]; // so get it from there...
pak->data_size -= 2; // ...and correct the data size
return (checksum); // finished, pak is decrypted
}
Comme vous pouvez le constater, après décryptage, la taille du bloc de données a raccourci de deux octets. On retrouve l'opération inverse dans la fonction d'encryption :
unsigned long Pak_SwitchEncryptionOn_150 (pak_t *pak)
{
// the 1.50 protocol cryptography uses extensively the standard C random number generator,
// which is a VERY BAD idea, since its implementation may differ from system to system !!!
static unsigned char reverse_tree[21] = {0, 5, 2, 9, 11, 1, 18, 7, 14, 3, 10, 4, 12, 13, 8, 15, 19, 20, 6, 16, 17};
unsigned long seed;
unsigned long return_seed;
char stack1[10];
char stack2[10];
unsigned char a;
unsigned char c;
char *edi;
char *ebp;
int index;
unsigned int algo;
seed = lrand (); // generate a random seed for this pak's encryption
// initialize the system's pseudo-random number generator from the seed used in the datagram
// (they apparently swapped the bytes in an attempt to confuse the reverse-engineerers)
srand (seed);
// write the seed in the pak
((unsigned char *) &return_seed)[0] = (unsigned char) (seed >> 24);
((unsigned char *) &return_seed)[3] = (unsigned char) (seed >> 16);
((unsigned char *) &return_seed)[1] = (unsigned char) (seed >> 8);
((unsigned char *) &return_seed)[2] = (unsigned char) (seed >> 0);
// if new data size requires us to allocate more pages for pak data, do it
if ((pak->data_size + 2) / 1024 + 1 > pak->pages_count)
{
pak->data = (char *) SAFE_realloc (pak->data, pak->pages_count, (pak->data_size + 2) / 1024 + 1, 1024, false);
pak->pages_count = (pak->data_size + 2) / 1024 + 1; // new number of pages
}
// compute the pak checksum and append this information to the data
*(unsigned short *) &pak->data[pak->data_size] = Pak_ComputeChecksum_150 (pak);
pak->data_size += 2; // correct pak data size
// now generate the crypto tables for the given datagram length
// stack sequences
for (index = 0; index < 10; index++)
{
stack1[index] = (char) rand ();
stack2[index] = (char) rand ();
}
// xor table
for (index = 0; index < pak->data_size; index++)
{
cryptotables_150.xor[index] = (unsigned char) stack2[rand () % 10];
cryptotables_150.xor[index] *= (unsigned char) stack1[rand () % 10];
cryptotables_150.xor[index] += rand ();
}
// offset & algo tables
for (index = 0; index < pak->data_size; index++)
{
cryptotables_150.offsets[index] = rand () % pak->data_size;
if (cryptotables_150.offsets[index] == (unsigned int) index)
cryptotables_150.offsets[index] = (index == 0 ? 1 : 0);
cryptotables_150.algo[index] = rand () % 21;
}
// cryptographic tables are generated, now quadruple-XOR the data out
for (index = pak->data_size - 1; index >= 0; index--)
{
if (index <= pak->data_size - 4)
{
pak->data[index + 0] ^= (cryptotables_150.xor[index] & 0x000000FF); // we can XOR 4 bytes in a row
pak->data[index + 1] ^= (cryptotables_150.xor[index] & 0x0000FF00) >> 8;
pak->data[index + 2] ^= (cryptotables_150.xor[index] & 0x00FF0000) >> 16;
pak->data[index + 3] ^= (cryptotables_150.xor[index] & 0xFF000000) >> 24;
}
else if (index == pak->data_size - 3)
{
pak->data[index + 0] ^= (cryptotables_150.xor[index] & 0x0000FF); // we can XOR 3 bytes in a row
pak->data[index + 1] ^= (cryptotables_150.xor[index] & 0x00FF00) >> 8;
pak->data[index + 2] ^= (cryptotables_150.xor[index] & 0xFF0000) >> 16;
}
else if (index == pak->data_size - 2)
{
pak->data[index + 0] ^= (cryptotables_150.xor[index] & 0x00FF); // we can XOR 2 bytes in a row
pak->data[index + 1] ^= (cryptotables_150.xor[index] & 0xFF00) >> 8;
}
else if (index == pak->data_size - 1)
pak->data[index] ^= (cryptotables_150.xor[index] & 0xFF); // end of stream
}
// and finally, apply the algorithm
for (index = pak->data_size - 1; index >= 0; index--)
{
// actually, reverse_tree is a switch table hardcoded by cl compiler (MSVC)
algo = reverse_tree[cryptotables_150.algo[pak->data_size - 1 - index]];
ebp = &pak->data[cryptotables_150.offsets[pak->data_size - 1 - index]];
edi = &pak->data[pak->data_size - 1 - index];
a = *ebp;
c = *edi;
if (algo == 0) { *edi = ((a ^ c) & 0x0F) ^ c; *ebp = ((a ^ c) & 0x0F) ^ a; }
else if (algo == 1) { *edi = ((a ^ c) & 0x0F) ^ c; *ebp = (a >> 4) | (c << 4); }
else if (algo == 2) { *edi = (c >> 4) | (c << 4); *ebp = (a >> 4) | (a << 4); }
else if (algo == 3) { *edi = (a >> 4) | (c << 4); *ebp = ((a ^ c) & 0x0F) ^ c; }
else if (algo == 4) { *edi = (a & 0x0F) | (c << 4); *ebp = (a & 0xF0) | (c >> 4); }
else if (algo == 5) { *edi = (c & 0xF0) | (a >> 4); *ebp = (a << 4) | (c & 0x0F); }
else if (algo == 6) { *edi = (a >> 4) | (c << 4); *ebp = (a << 4) | (c >> 4); }
else if (algo == 7) { *edi = (c & 0xF0) | (a >> 4); *ebp = (a & 0x0F) | (c << 4); }
else if (algo == 8) { *edi = (a & 0x0F) | (c << 4); *ebp = (c & 0xF0) | (a >> 4); }
else if (algo == 9) { *edi = (a & 0xF0) | (c >> 4); *ebp = (a & 0x0F) | (c << 4); }
else if (algo == 10) { *edi = (a << 4) | (c & 0x0F); *ebp = (a & 0xF0) | (c >> 4); }
else if (algo == 11) { *edi = (a << 4) | (c >> 4); *ebp = ((a ^ c) & 0x0F) ^ a; }
else if (algo == 12) { *edi = (a >> 4) | (a << 4); *ebp = (c >> 4) | (c << 4); }
else if (algo == 13) { *edi = a; *ebp = c; }
else if (algo == 14) { *edi = (a & 0xF0) | (c >> 4); *ebp = (a << 4) | (c & 0x0F); }
else if (algo == 15) { *edi = ((a ^ c) & 0x0F) ^ a; *ebp = ((a ^ c) & 0x0F) ^ c; }
else if (algo == 16) { *edi = a; *ebp = (c >> 4) | (c << 4); }
else if (algo == 17) { *edi = (a << 4) | (c & 0x0F); *ebp = (c & 0xF0) | (a >> 4); }
else if (algo == 18) { *edi = (a << 4) | (c >> 4); *ebp = (a >> 4) | (c << 4); }
else if (algo == 19) { *edi = (a >> 4) | (a << 4); *ebp = c; }
else if (algo == 20) { *edi = ((a ^ c) & 0x0F) ^ a; *ebp = (a << 4) | (c >> 4); }
}
return (return_seed); // finished, pak is encrypted
}
Comme vous pouvez le constater, lors de l'opération d'encryption, la taille des données du pak grandit de deux octets pour pouvoir stocker la somme de contrôle à la fin. Attention à ne pas oublier de réallouer cet espace dans votre fonction.
Méthode de calcul de la somme de contrôle
La somme de contrôle est l'addition octet par octet des octets des valeurs inverses données du pak, le résultat étant représenté sur deux octets non signés (dépassement de capacité compris).
signed short Pak_ComputeChecksum_150 (pak_t *pak)
{
// this function computes and returns the pak data's checksum
unsigned long index;
signed short sum;
sum = 0; // start at zero
// for each byte of data...
for (index = 0; index < pak->data_size; index++)
sum += (unsigned char) (~pak->data[index]); // add its inverse value to the checksum
return (sum); // return the checksum we found
}
Format des datagrammes UDP de T4C
Pak non fragmenté
Taille Type Description =============================================================================== 1 unsigned char 0 1 unsigned char Type de datagramme (tableau de bits) 2 unsigned short Longueur du datagramme 4 unsigned long ID du datagramme 4 unsigned long 0 4 unsigned long Clé de cryptage du pak * char * Données du pak (cryptées), encodées en "big endian" 2 signed short Somme de contrôle (cryptée également) des données du pak NON CRYPTEES
Pak fragmenté
Premier fragment
Taille Type Description =============================================================================== 1 unsigned char 0 1 unsigned char Type de datagramme (tableau de bits) 2 unsigned short Longueur du header + clé + données bout à bout y compris checksum 4 unsigned long ID du datagramme 4 unsigned long copie de l'ID du datagramme 4 unsigned long Clé de cryptage du pak 1008 char * Données du pak (cryptées), encodées en "big endian"
Fragment quelconque, hormis le dernier
Taille Type Description =============================================================================== 1 unsigned char 0 1 unsigned char Type de datagramme (tableau de bits) 2 unsigned short Longueur du header + clé + données bout à bout y compris checksum 4 unsigned long ID du datagramme 4 unsigned long ID du datagramme de référence (premier de la série) 1012 char * Données du pak (cryptées), encodées en "big endian"
Dernier fragment
Taille Type Description =============================================================================== 1 unsigned char Numéro du fragment 1 unsigned char Type de datagramme (tableau de bits) 2 unsigned short Longueur du header + clé + données bout à bout y compris checksum 4 unsigned long ID du datagramme 4 unsigned long ID du datagramme de référence (premier de la série) * char * Données du pak (cryptées), encodées en "big endian" 2 signed short Somme de contrôle (cryptée également) des données du pak NON CRYPTEES (checksum)
Attention : 0 est un ID de pak valide !
Valeurs des différents champs
Tableau de bits du type de datagramme
Cet octet considéré comme un tableau de bits permet au datagramme de transporter jusqu'à huit flags différents. Voici ceux utilisés par le jeu :
- 00000000 (0x00): aucun flag, datagramme normal
- 00000001 (0x01): ce datagramme est l'accusé de réception d'un autre datagramme
- 00000010 (0x02): ce datagramme demande un accusé de réception
- 00000100 (0x04): ce datagramme contient un fragment de pak fragmenté
Ces valeurs peuvent bien entendu être combinées. Ainsi, un datagramme porteur des flags 00000110 sera un fragment de pak fragmenté qui demande un accusé de réception.
ID du datagramme
Il s'agit d'un numéro unique, normalement incrémentiel, mais qui peut parfaitement être aléatoire. Le premier datagramme envoyé par le client a toujours l'ID 0. Ensuite, le serveur renvoie un pak porteur de l'ID à partir de laquelle le client peut commencer son incrémentation.
Système d'accusé de réception
UDP étant un protocole non fiable, quand les données à envoyer sont importantes et nécessitent l'assurance que le destinataire (client ou serveur) les reçoit effectivement, les datagrammes envoyés portent le flag ACK REQUEST (deuxième bit le moins significatif dans l'octet du type de datagramme).
Quand l'un des hôtes reçoit un datagramme portant le flag ACK REQUEST, il doit aussitôt renvoyer un datagramme accusé de réception au format suivant (il s'agit d'une en-tête vide, portant seulement le flag ACK REPLY et l'ID du datagramme concerné).
Accusé de réception :
Taille Type Description =============================================================================== 1 unsigned char 0 1 unsigned char 1 2 unsigned short 0 4 unsigned long ID du datagramme faisant l'objet d'un accusé de réception 4 unsigned long 0
Ce système est implémenté au niveau DATAGRAMME, et non au niveau des paks assemblés. Ce qui signifie que si un pak fragmenté demande un accusé de réception, il le demande dans le header de chacun de ses fragments et il convient d'envoyer un accusé de réception à chaque fragment de pak reçu.
--Sorkvild 16 mars 2008 à 04:08 (MSK)