KTP - Exemple de génération de clé CD

Soyez le premier à donner votre avis sur cette source.

Vue 3 909 fois - Téléchargée 1 500 fois

Description

Bonjour,

Ce programme est un exemple de génération de
clé CD (format 16 caractères de A à Z et de 0 à 9.

Le but était au départ pour moi, de voir comment faire (j'ai en projet, de faire
un programme de génération de clé CD par documents XML ; KTP est un moyen de voir
comment faire).

Le format de la clé est de mon invention, voir le fichier .txt à l'intérieur qui
explique en détail le format et donne 3 exemples (générés à la main).

En démarrant le programme avec la commande -h ou --help, vous aurez accès
à l'aide pour comprendre son utilisation.

Le programme génère des clés, mais peut aussi les vérifier.

KTP est dnas le domaine public, vous pouvez donc copier en intégralité le code
sans aucun soucis et je vous y engage d'ailleurs à le faire.

Si vous avez des idées d'amélioration, je suis preneur (j'en ai déjà une en tête).

J'ai pas mal commenté le code et généré une documentation doxygen pour comprendre
ce que chaque fonction fait (sauf celles du main qui sont pas documentées).

J'ai aussi tenté de faire au plus simple et optimiser, mais je pense qu'il doit
y avoir encore pas mal de boulot pour un code plus propre.

J'espère que ce petit programme (fait en quelques jours seulement), vous permettra
d'y voir plus clair dans la jungle qu'est le principe de création de clé de
protection pour des programmes/jeux.

Codes Sources

A voir également

Ajouter un commentaire

Commentaires

Messages postés
256
Date d'inscription
samedi 3 janvier 2004
Statut
Membre
Dernière intervention
30 mai 2016
4
MErci pour tes précieux conseils.

Pour le C++11, je ne sais pas si ma version actuelle de GCC le gère en totalité ou en partie. Mais surtout, ça risque pas d'être incompatible avec les vieux systèmes ? Car mon but est que mes programmes passent sur les plus vieux windows possible (au moins XP).
Messages postés
3813
Date d'inscription
dimanche 12 décembre 2004
Statut
Modérateur
Dernière intervention
12 juin 2020
107
Mon programme qui (comme je l'ai souligné), utilisera des fichiers XML définissant comment la clé est créée.
Pour l'autre se sera différent, aussi il utilisera une lib (libxml2) pour le XML.
Si tu en as la possiblité, évite le XML. C'est une mode du début des années 2000, de moins en moins utilisée. C'est lourd à parser, très verbeux, et aisément remplaçable par autre chose. Par exemple pour des fichiers de configurations, on préfère mettre des couples clés/valeurs, voir une base locale SQLite (Si tu ne connais pas, c'est du SQL qui tient dans un fichier local, sans utiliser de serveur). Dans le milieu du web, on a depuis longtemps abandonné XML au profit de Json, bien plus compacte et rapide à parser. A moins d'y être contraint, le format XML est rarement une bonne idée...

Pour ton cas particulier, un simple fichier de texte contenant ta ou tes regexp(s) sera suffisant.

Pour la méthode random, j'ai lu sur le net qu'il fallait des integer, d'où le fait que je n'ai pas passé de char mais des integer (et donc les dits commentaires). Merci donc pour l'information.
Les types "pod" (int, char, float, etc...) sont compatibles entre eux sans cast. Tant que tu fais rentrer un type plus petit dans un plus grand, il n'y aura jamais de souci. Faire rentrer un char dans int est sans danger, l'inverse n'est pas vrai (valeur tronquée).

Quand aux cast, je connais ceux en C mais hélas pas ceux en C++ (static_cast dont j'ai entendu parlé mais jamais réussi à l'utiliser).
Tu as 4 casts en C++:
- reinterpret_cast: Permet de transformer un type en tout autre chose. Exemple classique: fait passer un pointeur "void*" dans un entier "int". Il est rare qu'ont ait à l'utiliser. C'est à employer avec grande parcimonie.
- static_cast: C'est le plus courant. Il transforme un type en un autre. Exemple: "void*" en "int*", ou "float*" en "int*".
- const_cast: Permet de faire sauter un const. Exemple: "const char*" vers "char*". Son utilisation est signe de mauvaise conception dans 99% des cas.
- dynamic_cast: Tente de convertir un pointeur sur classe en un autre compatible. Exemple: Si B hérite de A, alors on peut faire un dynamic_cast de B* vers A*. Si on tente un dynamic_cast sur quelque chose de non compatible, renvoi 0. Opération couteuse à éviter, puisque c'est le seul cast qui est fait à l'exécution et non à la compilation.

Le seul qui est vraiment "gratuit" et qui est le moins dangereux, c'est le static_cast. Par défaut, si tu ne sais pas quoi faire, utiliser un static_cast.
Pour info, le C cast est un "fourre-tout", qui réalise les casts décrits précédemment dans cet ordre là:
* const_cast
* static_cast
* static_cast puis const_cast
* reinterpret_cast
* reinterpret_cast puis const_cast

On préfère généralement avoir une erreur de compilation, plutôt que d'arriver silencieusement dans le dernier cas.

Exemple de cast C vs C++:
int* i = 0;
char* s = 0;

i = (char) s; // C
i = static_cast<char*>(s); // C++


Si tu as d'excellents liens sur le C++, je suis preneur.
Plus que des liens, il y a surtout des livres réputés sur le C++. Je te link des références de bouquins par MP.

Si tu en as la possibilité, je t'invite fortement à passer au C++11 (la nouvelle version du C++ officialisée comme son nom l'indique en 2011). C'est entièrement compatible avec le C++ standard, mais ça ajoute énormement de fonctionnalités en plus, corrige des bugs historiques (le fameux bug de parsing du "> >" dans un template), et améliore les performances (via les std::move).

Un exemple tout bête qui change la vie.
Avant:
std::vector<int> tab;
for (std::vector<int>::const_iterator it = tab.begin(); it != tab.end(); ++it)
  std::cout << *it << std::endl;


Après:
std::vector<int> tab;
for (int& i : tab)
  std::cout << i << std::endl;


La liste des fonctionnalités ici: http://en.wikipedia.org/wiki/C%2B%2B11
Messages postés
256
Date d'inscription
samedi 3 janvier 2004
Statut
Membre
Dernière intervention
30 mai 2016
4
Bonjour à toi,

Merci pour ce très long commentaire. Et si tu me le permet je souhaite te répondre.

Concernant les regexp et le fait d'utiliser un fichier de définition plutôt que du codé en dur, c'est ce qui est prévu sur mon "vrai" programme. En fait, je me suis toujours demandé comment créer un système de génération et de vérification de clé CD (système qui plus tard sera utilisé sur mes programmes, mais pas dans un but de protection mais simplement d'apporter des fonctionnalités ou aide supplémentaire suivant si on a la clé ou non) ; KTP n'est là que pour la mise en pratique de cette interrogation, et devait à la base n'être que pour moi, afin de m'en servir comme modèle pour créer mon programme qui (comme je l'ai souligné), utilisera des fichiers XML définissant comment la clé est créée.
Simplement, comme j'y ai appris pas mal de choses au milieu, je me suis dit que mettre le programme ici serait une bonne idée. ;)
Par contre, je souhaite ne pas dépendre d'une bibliothèque externe, aussi dans le cas de ce projet j'ai choisie de tout faire avec l'API standard. Pour l'autre se sera différent, aussi il utilisera une bibliothèque (libxml2) pour le XML et certainement Boost pour d'autres trucs dont la génération aléatoire en effet.

Pour le if, si j'utilise le point d'exclamation au lieu du == false, il faisait l'inverse ; je ne saurais te dire d'ailleurs pourquoi, mais c'est pour ça que je me suis rabattu sur ce que j'ai fait.

Pour les extensions, je suis effectivement habitué au .h plutôt que .hpp, d'où le fait que j'utilise toujours .h, mais j'ai rapidement pris la bonne habitude du .cpp au lieu du .c ; va savoir pourquoi j'arrive sur l'un et l'autre non lol.

Pour les types je ne savais pas, ayant appris tout seul, j'ai vu les int etc. mais je connaissais pas les autres types dont tu parles ; merci pour l'information.

Pour la méthode random, j'ai lu sur le net qu'il fallait des integer, d'où le fait que je n'ai pas passé de char mais des integer (et donc les dits commentaires). Merci donc pour l'information.


Pour le reste, merci pour les conseils et surtout les liens.
Quand aux cast, je connais ceux en C mais hélas pas ceux en C++ (static_cast dont j'ai entendu parlé mais jamais réussi à l'utiliser). Si tu as d'excellents liens sur le C++, je suis preneur.
Messages postés
3813
Date d'inscription
dimanche 12 décembre 2004
Statut
Modérateur
Dernière intervention
12 juin 2020
107
Bonjour.

== Au niveau fonctionnel ==

Le code respecte bien les spécifications. Néanmoins, c'est dommage que ce soit en "dur". Un axe d'amélioration intéressant, aurait été d'avoir un fichier de "description", suivant les conventions décrites dans "codages_specs.txt", et que le progamme s'adapte dynamiquement à ces règles. A vrai dire, on pourrait très bien remplacer ce fichier de specs par une expression rationnelle, qui s'acquitte parfaitement de cette tâche. Au final, ton programme peut se simplifier en "vérifier que la regex est bonne" et "générer un pattern compatible avec la regex" (c'est un simple automate, très simple à réaliser). En utilisant du boost::regex, on peut surement simplifier le code énormément, tout en le rendant plus dynamique.
C'est un axe d'amélioration qui a du potentiel, mais qui s'éloigne de ce que tu as fait (la simple utilisation de boost::regex, serait de la "triche", puisqu'on pourrait tout faire en deux fonctions).
Enfin, pour générer ce genre de clé, plutôt que du C++ on utilisera plutôt du sed ou du perl, qui réaliseront ce que tu as fait en 2 lignes.



== Au niveau technique ==

Y a des petites inélégances techniques, notamment sur l'utilisation de C au lieu de C++. Si je ne le détaille pas, je parle de C++03 (classique), sinon je parle de C++11.

- D'une manière générale, en C++ on utilise std::string et non char*. Sa simple présence, bien que possible, est généralement suspecte.
- Si on a besoin d'un tableau de caractère à taille fixe, on peut utiliser std::array (C++11).
- Quand on compare un booléen, on écrit "if (!val)" ou "if (val)" plutôt que "if (val == false)" ou "if (val == true)".
- On préfère 0 à NULL en C++. (Voir mieux: le mot clé "nullptr", à partir de C++11). Voir: http://0217021.free.fr/portfolio/axel.berardino/articles/null-en-cpp
- Les extensions de fichier en C++ sont ".cc/.hh" ou ".cpp/.hpp". Le ".h" étant plutôt reservé au C.
- Pour des raisons de portabilité, on préfère généralement utiliser les types "uint32_t", "uint16_t", etc... Plutôt que "unsigned int", "unsigned short", etc... Tu peux trouver ces types dans <stdint.h>. C'est une remarque facultative. C'est généralement une problématique rencontrée dans des cas assez particuliers (compilation sur des architectures différentes).
- A partir de C++11, on a enfin un vrai générateur de nombre aléatoire ! Si tu y accès, tu peux avantageusement remplacer le vieux couple srand/rand, par un std::mersenne_twister (ou tout autre algo de génération de nombre aléatoire fournit par la nouvelle version du C++).
- Au niveau lisibilité, tu as bien compris que "random(66, 72);" n'était pas très explicite, raison pour laquelle tu as ajouté des commentaires du type "//B-H". Néanmoins, on peut se passer de tout ces commentaires, et directement écrire quelque chose de lisible. Etant donné que 66 et 'B', c'est la même chose en C et C++, tu peux écrire: "random('B', 'H');". La même remarque s'applique à tous les "if" et "random()" de ton code.
- Toutes les classes héritant de std::fstream, se "close()" automatiquement au sortir d'un scope. Le "stream.close();" n'est donc pas nécessaire.
- Plutôt qu'un std::fstream + std::ios::out, on peut utiliser directement un std::ofstream. Même remarque pour les std::ifstream.
Ex:
std::fstream stream;

stream.open(file, std::fstream::in);
while ( stream.eof() != true )
{
  stream.getline(buffer, 20);
  validate(buffer);
}
stream.close();

peut être remplacé par:
std::ifstream stream(file);
while (stream)
{
  stream.getline(buffer, 20);
  validate(buffer);
}

On utiliserait d'ailleurs plutôt:
std::string line;
std::ifstream stream(file);
while (std::getline(stream, line))
  validate(line.c_str());

- Dans les fonctions de type "CValide::grp", tu copies des valeurs dans un tableau temp, ce qui peut être évité. De plus, le cast n'est pas nécessaire.
- Juste pour précision, en C++, on évite les "C cast" au profit des cast C++ dont le comportement est sélectionnable (il y a 4 casts différents, à raison).



== Au niveau architecture ==

Je vais parler ici, de l'agencement du code, et du design des classes et fonctions.

- Quand une méthode ne modifie pas un attribut, on doit la marquer comme const. (Exemple: les méthodes CValide::grp).
- Quand une méthode n'utilise pas d'attribut de classe ou n'appelle pas une autre méthode, alors il y a erreur de design. La méthode devrait être une fonction interne, visible uniquement par la classe. Pour réaliser cela, on met la fonction dans un namespace anonyme, dans le fichier d'implémentation de la classe (au-dessus des autres méthodes, pour qu'elle soit "vue"). C'est par exemple le cas pour la fonction "random".
- Dans le main, toutes les fonctions devraient être au dessus du main, dans un namespace anonyme.
- Plutôt que plein de "if" à la suite, il y a plusieurs endroits où il est plus adapté d'y mettre des "switch". Ce n'est d'ailleurs pas simplement plus élégant, mais aussi plus rapide.
- Quitte à utiliser du C pour lire les options de la ligne de commande autant le faire franchement. Il y a la méthode "getopt", qui lit déjà des options à partir de argc, argv, en gérant tous les cas (ordre des paramètres, doublon, etc...).
http://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html
En C++, on utilise géneralement "boost::program_option". http://www.boost.org/doc/libs/1_55_0/doc/html/program_options/tutorial.html

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.