Un compteur d'accès intégré à l'exécutable d'un programme

Partagez votre expérience de PureBasic avec les autres utilisateurs.
fweil
Messages : 505
Inscription : dim. 16/mai/2004 17:50
Localisation : Bayonne (64)
Contact :

Un compteur d'accès intégré à l'exécutable d'un programme

Message par fweil »

Voilà j'espère quelques commentaires ...

Je crois que cela répondra à plusieurs posts ici et sur le forum EN.

Code : Tout sélectionner

;
; Workaround for a program usage counter
; I guess it is interesting and well documented.
; This shows how to manage a usage counter encrypted in the executable itself.
; So you should save the source at a given place with a choosen name and compile it.
; For testing it properly, run the executable.
; As it is, this workaround will inform you of the license expiration after 3 runs.
;
; Read carefully comments and also I hope variable names are clear enough for understanding this.
;
; By extension, it is easy now to hard code ie the executable name in the same way and binary area, so that if
; the first executable name is not the same that the running, you can freeze the app, or do anything you want.
; ----------------------------------------------------
; Essai de cryptage d'un compteur d'utilisation d'un prog
; Je pense que c'est intéressant et bien documenté.
; Ce programme montre comment gérer un compteur d'utilisation encodé dans l'exécutable lui-même.
; Il faut donc sauver le source en lui donnant un nom et le compiler.
; Pour tester correctement le fonctionnement, lancer l'exécutable.
; Tel quel, vous devriez être informé que la licence est expirée après 3 lancements.
;
; Lisez bien les commentaires, en vous aidant des noms de variables pour une bonne compréhension.
;
; Par extensio, il est maintenant facile de coder en dur le nom de l'exécutable et de le comparer
; à lexécutable en cours pour geler l'application ou interagir comme vousl  le souhaitez.
;
; FWeil 20040707 : do not use this code without to inform me at fweil@internext.fr or on Purebasic forums.
;
; And you know what ? I am quite happy of this listing
;
;
; Set the size of the program messages array
; ----------------------------------------------------
; Fixe la taille du tableau des messages programme
;
#Max_Messages = 10

;
; Program messages array
; Tableau des messages du programme
;
Dim Messages.s(#Max_Messages)

;
; Procedure GetUserLanguage() : user language detection
; ----------------------------------------------------
; Procedure GetUserLanguage() : détection de la langue utilisateur
;
Procedure.s GetUserLanguage()
  Language.s
  GetSystemDefaultLangID = GetSystemDefaultLangID_()
  Select GetSystemDefaultLangID
    Case 1036   ; = French (Standard)
      Language = "FR"
    Default
      Language = "EN"
  EndSelect
  ProcedureReturn Language
EndProcedure

;
; Procedure Encrypt() : string encryption. The string can be decrypted using Decrypt()
; Both Encrypt() / Decrypt() procedures are symetrical, usable, but simple. You can change it by any encrypt / decrypt process you know or design.
; Just if you adapt this code, do not forget that the reserved space for the key is assigned by the second data of the KeyManagement: label in data section
; ----------------------------------------------------
; Procedure Encrypt() : encodage d'une chaine de caractères. La chaine peut être décodée avec Decrypt()
; Les procédures Encrypt() / Decrypt() sont symétriques, utilisables, mais simples. On peut les changer par tout procédé de codage / décodage connu ou personnalisé.
; Si ce code est changé, il faut simplement ne pas oublier que l'espace réservé pour la clé est défini par la deuxième donnée de la section data identifiée par KeyManagemen:
;
Procedure.s Encrypt(String.s)
  Result.s = ""
  For i = 1 To Len(String)
    Cod = Asc(Mid(String, i, 1))
    Result + Chr(((Cod & $F0) >> 4) + ($80 + Random($7F) & $F0)) + Chr((Cod & $0F) + ($80 + Random($7F) & $F0))
  Next
  ProcedureReturn Result
EndProcedure

;
; Procedure Decrypt() : string decryption using symmetrical algorythm in connection with Encrypt()
; ----------------------------------------------------
; Procedure Decrypt() : décodage d'une chaine de caractères en utilisant l'algorithme symétrique de Encrypt()
;
Procedure.s Decrypt(String.s)
  Result.s = ""
  For i = 1 To Len(string) Step 2
    Result + Chr(((Asc(Mid(String, i, 1)) & $0F) << 4) + (Asc(Mid(String, i + 1, 1)) & $0F))
  Next
  ProcedureReturn Result
EndProcedure

  ProgramName.s = Space(#MAX_PATH)
  GetModuleFileName_(0, @ProgramName, #MAX_PATH)
  ;
  ; User language detection and data section positioning to load program messages accordingly
  ; ----------------------------------------------------
  ; Détection de la langue utilisateur et placement approprié dans la zone data pour lire les messages du programme
  ;
  Select GetUserLanguage()
    Case "FR"
      Restore FR
    Default
      Restore EN
  EndSelect
  
  CurrentMessage = 0
  Repeat
    Read Message.s
    Messages(CurrentMessage) = Message
    CurrentMessage + 1
  Until Message = "EndDataSection"
  
  ;
  ; Data section positioning and read of the key to use to find the place of the usage counter
  ; ----------------------------------------------------
  ; Positionnement dans la zone data pour trouver la clé permettant d'accéder au compteur d'utilisation
  ;
  Restore KeyManagement
  Read Patch_Marker.s
  ;
  ; Program launch loading the encrypted 'usage counter' information
  ; This information is decrypted and parsed, and eventually updated information is encrypted again for saving in the file.
  ; ----------------------------------------------------
  ; Lancement du programme avec chargement de l'information 'compteur d'utilisation' encryptée
  ; Cette information est décryptée et analysée, puis mise à jour est ré-encryptée et enregistrée dans le fichier.
  ;
  If ReadFile(0, ProgramName)
      *Buffer = AllocateMemory(Lof())
      ReadData(*Buffer, Lof())
      For i = 0 To Lof()
        If PeekS(*Buffer + i, 6) = Patch_Marker
            CounterAddress = *Buffer + i + 6 ; CounterAddress is the address where the counter stays in the memory buffer
            Break
        EndIf
      Next
      If i => Lof()
          MessageRequester(Messages(0), Messages(1), #PB_MessageRequester_OK)
      EndIf
      CloseFile(0)
    Else
      MessageRequester(Messages(0), Messages(1), #PB_MessageRequester_OK)
  EndIf
  If CounterAddress
      Usage_Counter = Val(Decrypt(PeekS(CounterAddress, 6)))
      CounterAddress - *Buffer ; now the counter address is set to the file address, no more in the memory buffer
    Else
      Usage_Counter = 0
  EndIf

  FreeMemory(*Buffer)
  
  ;
  ; The counter value is placed in a buffer for editing in the file
  ; ----------------------------------------------------
  ; Le compteur est copié dans un buffer pour mise à jour du fichier
  ;
  *Key = AllocateMemory(6)
  Usage_Counter + 1
  PokeS(*Key, Encrypt(RSet(Str(Usage_Counter), 3, "0")))

  ;
  ; Here is the executable patch with the updated count
  ; ----------------------------------------------------
  ; Ici se trouve le patch du code exécutable avec le compteur à jour
  ;
  If OpenFile(0, ProgramName)
      FileSeek(CounterAddress)
      Debug WriteData(*Key, 6)
      CloseFile(0)
  EndIf
  
  ;
  ; In this skeleton I set the counter to 2. Don't forget to change this if you want to be smart
  ; ----------------------------------------------------
  ; Pour ce jeu d'essai j'ai mis le compteur à 2. N'oubliez pas de changer cette valeur pour être sympa dans vos applications
  ;
  If Usage_Counter > 2
      MessageRequester(Messages(0), Messages(2), #PB_MessageRequester_OK)
      Quit = #TRUE
    Else
      Quit = #FALSE
  EndIf

  ;
  ; From there the job is finished for the usage counter and you can place any regular app just using Quit #TRUE / #FALSE to do something or exit.
  ; ----------------------------------------------------
  ; A partir de là la gestion de compteur est terminée et on peut placer tout code de programme sous le contrôle éventuel de Quit #TRUE / #FALSE par exemple.
  ;
  If Quit = #FALSE
      MessageRequester("OK", "OK", #PB_MessageRequester_OK)
  EndIf
End

;
; Data section contains two data blocks corresponding to accepted languages
; ----------------------------------------------------
; La zone data contient les messages du programme avec un bloc pour chaque langue reconnue
;
DataSection

  FR:
  
  Data.s "Alerte"
  Data.s "Le programme ne peut être lancé : installation défaillante"
  Data.s "Le programme a été utilisé au delà de la licence accordée"
  Data.s "EndDataSection"
  
  EN:
  
  Data.s "Warning"
  Data.s "Can't run the program : bad installation"
  Data.s "Program used more than the license authorized"
  Data.s "EndDataSection"

EndDataSection

DataSection
  KeyManagement:
  ;
  ; Marker string to identify the position to use in the binary file
  ; ----------------------------------------------------
  ; Chaine marqueur pour identifier la position à utiliser dans le fichier binaire
  ;
  Data.s "SKBLZZ"
  ;
  ; Dummy string used as a reserved space in the binary file. This dummy string should not contain a valid number as Val(String) must equal 0.
  ; It is there only to lay in the binary code after compiling, and it will be changed each time the user will run the executable.
  ; ----------------------------------------------------
  ; Chaine factice utilisée comme réservation d'espace dans le fichier binaire. Cette chaine ne doit pas contenir une valeur numérique car la fonction
  ; Val de la chaine doit retourner 0. Cette chaine est placée de manière à se trouver à la bonne place dans le code généré par le compilateur, et
  ; sera changée à chaque lancement du programme par l'utilisateur.
  ;
  Data.s "TOTOTO"
EndDataSection
Mon avatar reproduit l'image de 4x1.8m présentée au 'Salon international du meuble de Paris' en janvier 2004, dans l'exposition 'Shades' réunisant 22 créateurs autour de Matt Sindall. L'original est un stratifié en 150 dpi.
fweil
Messages : 505
Inscription : dim. 16/mai/2004 17:50
Localisation : Bayonne (64)
Contact :

Message par fweil »

Il y a un bug pour l'instant.

Une chose que je n'avais pas vu en phase de test, c'est que je lançais le programme depuis l'éditeur, ce qui mettait l'exécutable compilé à jour, mais si on lance l'exécutable sur lui-même les autorisations de partage n'existent pas.

Je cherche une solution.

A défaut, je tuerai ce post ce soir.
Mon avatar reproduit l'image de 4x1.8m présentée au 'Salon international du meuble de Paris' en janvier 2004, dans l'exposition 'Shades' réunisant 22 créateurs autour de Matt Sindall. L'original est un stratifié en 150 dpi.
Patrick88
Messages : 1564
Inscription : mer. 21/janv./2004 18:24

Message par Patrick88 »

If Usage_Counter > 2
MessageRequester(Messages(0), Messages(2), #PB_MessageRequester_OK)
Quit = #TRUE
Else
Quit = #FALSE
EndIf

toutes ces complications pour terminer sur un bête test très facile à contourner... dommage :cry: cela dit, beuh, je serais bien incapable de cracher un tel code, donc bravo , (mais....)

patrick
comtois
Messages : 5186
Inscription : mer. 21/janv./2004 17:48
Contact :

Message par comtois »

http://purebasic.hmt-forum.com/viewtopic.php?t=267

le sujet avait été abordé ici .

Et je crois que le soldat inconnu avait posté un code quelque part suite à ce post ? là je ne suis plus très sûr.
fweil
Messages : 505
Inscription : dim. 16/mai/2004 17:50
Localisation : Bayonne (64)
Contact :

Message par fweil »

Désolé d'avoir manqué quelques rudiments avant de poster ce code la première fois.

J'ai donc modifié quelques lignes pour que le programme fonctionne (j'espère que c'est le cas maintenant), en utilisant un fichier externe.

Je ne souhaitais pas trop entrer dans des techniques complexes (comme la combinaison d'informations dans des fichiers multiples et des clés de registre). Donc je créé simplement un fichier dans le répertoire système. Une manière de montrer ce qu'il est possible de faire.

Le fichier contient un compteur codé avec une méthode d'encryptage / décryptage simple, juste pour en montrer le principe.

Code : Tout sélectionner

;
; Workaround for a program usage counter
; I guess it is interesting and well documented.
; This shows how to manage a usage counter encrypted in a key file placed in the system folder.
; As it is, this workaround will inform you of the license expiration after 3 runs.
;
; Read carefully comments and also I hope variable names are clear enough for understanding this.
;
; By extension, it is easy now to hard code information in the key file in the same way.
; ----------------------------------------------------
; Essai de cryptage d'un compteur d'utilisation d'un prog
; Je pense que c'est intéressant et bien documenté.
; Ce programme montre comment gérer un compteur d'utilisation encodé dans un fichier placé dans le répertoire sytème
; Tel quel, vous devriez être informé que la licence est expirée après 3 lancements.
;
; Lisez bien les commentaires, en vous aidant des noms de variables pour une bonne compréhension.
;
; Par extension, il est maintenant facile de coder en dur d'autres informations dans le fichier clé.
;
; FWeil 20040708 : do not use this code without to inform me at fweil@internext.fr or on Purebasic forums.
;
; And you know what ? I am quite happy of this listing
;

;
; Set the size of the program messages array
; ----------------------------------------------------
; Fixe la taille du tableau des messages programme
;
#Max_Messages = 10

;
; Program messages array
; Tableau des messages du programme
;
Dim Messages.s(#Max_Messages)

;
; Procedure GetUserLanguage() : user language detection
; ----------------------------------------------------
; Procedure GetUserLanguage() : détection de la langue utilisateur
;
Procedure.s GetUserLanguage()
  Language.s
  GetSystemDefaultLangID = GetSystemDefaultLangID_()
  Select GetSystemDefaultLangID & $FFFF
    Case 1036   ; = French (Standard)
      Language = "FR"
    Default
      Language = "EN"
  EndSelect
  ProcedureReturn Language
EndProcedure

;
; Procedure Encrypt() : string encryption. The string can be decrypted using Decrypt()
; Both Encrypt() / Decrypt() procedures are symetrical, usable, but simple. You can change it by any encrypt / decrypt process you know or design.
; ----------------------------------------------------
; Procedure Encrypt() : encodage d'une chaine de caractères. La chaine peut être décodée avec Decrypt()
; Les procédures Encrypt() / Decrypt() sont symétriques, utilisables, mais simples. On peut les changer par tout procédé de codage / décodage connu ou personnalisé.
;
Procedure.s Encrypt(String.s)
  Result.s = ""
  For i = 1 To Len(String)
    Cod = Asc(Mid(String, i, 1))
    Result + Chr(((Cod & $F0) >> 4) + ($80 + Random($7F) & $F0)) + Chr((Cod & $0F) + ($80 + Random($7F) & $F0))
  Next
  ProcedureReturn Result
EndProcedure

;
; Procedure Decrypt() : string decryption using symmetrical algorythm in connection with Encrypt()
; ----------------------------------------------------
; Procedure Decrypt() : décodage d'une chaine de caractères en utilisant l'algorithme symétrique de Encrypt()
;
Procedure.s Decrypt(String.s)
  Result.s = ""
  For i = 1 To Len(string) Step 2
    Result + Chr(((Asc(Mid(String, i, 1)) & $0F) << 4) + (Asc(Mid(String, i + 1, 1)) & $0F))
  Next
  ProcedureReturn Result
EndProcedure

  ;
  ; Look for the path of Windows system files
  ; ----------------------------------------------------
  ; Recherche du chemin des fichiers systèmes Windows
  ;
  WindowsDirectory.s = Space(#MAX_PATH)
  GetSystemDirectory_(WindowsDirectory.s, #MAX_PATH)
  
  ;
  ; Get the program filename
  ; ----------------------------------------------------
  ; Récupère le nom de fichier du programme
  ;
  ProgramName.s = Space(#MAX_PATH)
  GetModuleFileName_(0, @ProgramName, #MAX_PATH)
  
  ;
  ; Set the key filename
  ; ----------------------------------------------------
  ; Construit le nom du fichier clé
  ;
  KeyFilename.s = GetFilePart(ProgramName)
  KeyFilename = WindowsDirectory + "\" + Mid(KeyFilename, 1, FindString(KeyFilename, ".", 1) - 1) + ".key"
  
  ;
  ; User language detection and data section positioning to load program messages accordingly
  ; ----------------------------------------------------
  ; Détection de la langue utilisateur et placement approprié dans la zone data pour lire les messages du programme
  ;
  Select GetUserLanguage()
    Case "FR"
      Restore FR
    Default
      Restore EN
  EndSelect
  
  CurrentMessage = 0
  Repeat
    Read Message.s
    Messages(CurrentMessage) = Message
    CurrentMessage + 1
  Until Message = "EndDataSection"
  
  ;
  ; Open the key file to read the crypted content. If the file does not exist, the counter is set to 0
  ; ----------------------------------------------------
  ; Ouvre le fichier clé pour lire le contenu crypté. Si le fichier n'existe pas, le compteur est mis à 0
  ;
  If ReadFile(0, KeyFilename)
      LengthOfFile = Lof()
      *Buffer = AllocateMemory(LengthOfFile)
      ReadData(*Buffer, LengthOfFile)
      CloseFile(0)
      Key.s = PeekS(*Buffer)
      Usage_Counter = Val(Decrypt(Key)) + 1
      CloseFile(0)
    Else
      Usage_Counter = 0
  EndIf
  FreeMemory(*Buffer)
  
  ;
  ; Create a new key file with the updated encrypted counter
  ; ----------------------------------------------------
  ; Créé un nouveau fichier clé avec la nouvelle valeur encryptée du compteur
  ;
  If CreateFile(0, KeyFilename)
      WriteString(Encrypt(RSet(Str(Usage_Counter), 3, "0")))
      CloseFile(0)
    Else
      MessageRequester(Messages(0), Messages(1), #PB_MessageRequester_OK)
      End
  EndIf
  
  ;
  ; In this skeleton I set the counter to 2. Don't forget to change this if you want to be smart
  ; ----------------------------------------------------
  ; Pour ce jeu d'essai j'ai mis le compteur à 2. N'oubliez pas de changer cette valeur pour être sympa dans vos applications
  ;
  If Usage_Counter > 2
      MessageRequester(Messages(0), Messages(2), #PB_MessageRequester_OK)
      End
  EndIf

  ;
  ; From there the job is finished for the usage counter. Place your code after
  ; ----------------------------------------------------
  ; A partir de là la gestion de compteur est terminée. Placer votre code après
  ;
   MessageRequester("OK", Messages(3) + " " + Str(Usage_Counter) + " " + Messages(4), #PB_MessageRequester_OK)
End

;
; Data section contains two data blocks corresponding to accepted languages
; ----------------------------------------------------
; La zone data contient les messages du programme avec un bloc pour chaque langue reconnue
;
DataSection

  FR:
  
  Data.s "Alerte"
  Data.s "Le programme ne peut être lancé : installation défaillante"
  Data.s "Le programme a été utilisé au delà de la licence accordée"
  Data.s "Vous avez utilisé ce programme "
  Data.s "fois"
  Data.s "EndDataSection"
  
  EN:
  
  Data.s "Warning"
  Data.s "Can't run the program : bad installation"
  Data.s "Program used more than the license authorized"
  Data.s "You used this program "
  Data.s "time(s)"
  Data.s "EndDataSection"

EndDataSection
Mon avatar reproduit l'image de 4x1.8m présentée au 'Salon international du meuble de Paris' en janvier 2004, dans l'exposition 'Shades' réunisant 22 créateurs autour de Matt Sindall. L'original est un stratifié en 150 dpi.
hardy
Messages : 333
Inscription : mer. 02/juin/2004 13:19
Localisation : Tours

Message par hardy »

Le codage ne me semble pas nécessaire. Tout repose sur le secret concernant la localisation du compteur.
Si une personne sait où il est, il lui suffit de restaurer le fichier sauvé lors de la première utilisation.
Dans tous les cas c'est là la base du système de compteur.
Les programmes d'évaluation doivent en mettre un certain nombre (fichiers et clés de registre), et vérifier leur compatibilité.
Il y a certains programme de sécurisation (j'en connaît un en particulier utilisé par l'Encyclopédie Universalis entre autre, mais le nom m'échappe) qui logent des trucs dans la zone de boot.
Dans la majorité des cas ça doit pouvoir se contourner "assez facilement" en utilisant un programme qui traque les événements, et permet de voir tout ce que le programme fait en s'installant (création de fichiers et clés de registre).

D'ailleurs (par simple curiosité ! Je ne pirate rien.) j'envisage d'étudier la création d'un tel programme (observateur d'événements). Mais ça risque de prendre du temps, rien que pour se documenter...
Enfin, faut bien s'occuper !

@fweil : rien à voir, mais je n'ai toujours pas trouvé comment obtenir les adresses des buffers de l'écran "usuel" (l'équivalent de drawingbuffer et drawingbufferpitch). Toujours pas de nouvelle?
gansta93
Messages : 1448
Inscription : jeu. 26/févr./2004 11:17
Localisation : Le Village
Contact :

Message par gansta93 »

J'ai une question qui montre bien que je suis un débutant.
C quoi le $FFFF qui est dans le chois de la langue?

Code : Tout sélectionner

  Select GetSystemDefaultLangID & $FFFF
Tout ce que je sais, c'est que c un truc en hexadécimal!
fweil
Messages : 505
Inscription : dim. 16/mai/2004 17:50
Localisation : Bayonne (64)
Contact :

Message par fweil »

C'est pour ne tester que les 16 bits de poids faible ...

Exemple :

Valeur.l = 123456
Debug Hex(Valeur)
Debug Hex(Valeur & $FFFF0000)
Debug Hex(Valeur & $0000FFFF)
Mon avatar reproduit l'image de 4x1.8m présentée au 'Salon international du meuble de Paris' en janvier 2004, dans l'exposition 'Shades' réunisant 22 créateurs autour de Matt Sindall. L'original est un stratifié en 150 dpi.
gansta93
Messages : 1448
Inscription : jeu. 26/févr./2004 11:17
Localisation : Le Village
Contact :

Message par gansta93 »

Parce que c'est bizarre mais maintenant, la langue française est prise contrairemant à avant... et je n'ai pas eu à modifier quoi que ce soit! :-) génial.
Avatar de l’utilisateur
Chris
Messages : 3731
Inscription : sam. 24/janv./2004 14:54
Contact :

Message par Chris »

Il y a des petits progs qui permettent de faire apparaitre à l'écran tous les accès fichier d'un logiciel. Je pense, par exemple à FileMon.exe. C'est un freeware, dont la taille fait 92 Ko, très simple à utiliser, même par un débutant

En utilisant ce programme, tu peux facilement savoir ou se trouve le fichier clé, il ne reste plus qu'à le supprimer (un simple batch avec la commande Del peut faire l'affaire), pour avoir toujours un compteur à 0.

Tout ça pour dire que, niveau protection, ca me parait plutôt léger, puisque même un utilisateur n'ayant pas de connaissances en cracking peut en venir à bout facilement. Il suffit simplement d'un peu de logique. ;)

Et pour celui qui programme un peu, il suffit de créer un petit prog qui lance le logiciel protégé, qui détecte sa fermeture, et qui supprime automatiquement le fichier.

Code : Tout sélectionner

RepWin.s = Space(#MAX_PATH) 
GetSystemDirectory_(RepWin, #MAX_PATH)

KeyFile.s = RepWin+"\Prog.key"

While RunProgram("Prog.exe","","",1) <= 0
Wend

DeleteFile(KeyFile)
End

Chris ;)
gansta93
Messages : 1448
Inscription : jeu. 26/févr./2004 11:17
Localisation : Le Village
Contact :

Message par gansta93 »

En gros... un debugger?
Je vais l'essayer... peut-être que celui-là je le comprendrait au moins!:-)
hardy
Messages : 333
Inscription : mer. 02/juin/2004 13:19
Localisation : Tours

Message par hardy »

J'ai commencé à farfouiller côté PSDK.
Il y a effectivement tout l'attirail nécessaire pour tracer les événements : accès fichiers ou registre, etc...
Faut juste s'infuser 800000 structures, régler 3 millions de constantes et pointeurs, etc...
Quand j'aurai le courage j'essaierai. (c'est pas pour tout de suite !)
Je mettrai ici des morceaux de code si c'est probant.
gansta93
Messages : 1448
Inscription : jeu. 26/févr./2004 11:17
Localisation : Le Village
Contact :

Message par gansta93 »

Génial FileMon!!!!!!! :-D
Qq1 connaît-il un debugger simple mais complait qui me dirait même les clées de registre utilisées et tout?
Avatar de l’utilisateur
Chris
Messages : 3731
Inscription : sam. 24/janv./2004 14:54
Contact :

Message par Chris »

Pour les clés de registre, il y a RegMon.exe, sur le même site.

Chris :)
gansta93
Messages : 1448
Inscription : jeu. 26/févr./2004 11:17
Localisation : Le Village
Contact :

Message par gansta93 »

Ah ben dans ce cas-là... je vais passer là-bas!
Mais y a un truc que je ne comprand pas... sur les site où on peut le dl il disent qu'il n'est pas compatible win98 mais sur le site officiel ils disent qu'il l'est et il l'est vraimant donc... enfin pas grave.
Répondre