Retourner au sommaire

Le déplombage

Basé sur l'article publié dans Quasar CPC numéro 19, Assembleur : Software, par CNGSoft.

Dans cette rubrique nous aborderons les différentes techniques liées au déplombage des jeux, des utilitaires ou même des démos. Le but étant la mise en fichier pour une manipulation plus aisée.

Principes de base

Ne laissons pas mourir nos logiciels ! Le petit blond à lunettes lit le titre et dit : “Keskesesah ? Quasar favorise les actions illégales maintenant ?”. Non, non, pas de panique, ce n'est pas l'objectif de cet article. Je pense en fait à tout autre chose : comment conserver pour toujours ces jeux sauvés sur cassette avec des protections qui ne servent qu'à provoquer des erreurs et nous obligent à rembobiner encore et encore jusqu'à détruire la bande magnétique ? C'est un peu comme l'histoire des civilisations inconnues : leur oubli vient de l'impossibilité à comprendre leurs textes, alors que les civilisations plus tournées vers le futur faisaient l'effort d'être plus explicites. Ainsi, avec cet article, vous serez capables de sauver pour la postérité les programmes CPC.

Préliminaires

D'abord, c'est quoi une protection ? Il s'agit de l'utilisation de techniques de sauvegarde et récupération incompatibles avec les méthodes standard du système afin de rendre impossible la manipulation frauduleuse. Sur CPC, cela signifie enregistrer des cassettes avec des signaux illisibles sous BASIC, des disquettes refusant d'être lues par l'Amsdos, etc.

Mais les protections ne sont pas incontournables : après tout, il faut toujours un programme de chargement sans protection1) pour pouvoir être exécuté depuis le système ; et ce programme contient le code capable de charger les programmes protégés !

C'est alors que le déplombage2) est basiquement la manipulation du loader pour pouvoir charger le programme protégé et le sauvegarder sans protection pour pouvoir ensuite l'utiliser tranquillement. Voyons quelques exemples par ordre de difficulté.

Voilà qui ne plombera pas l'ambiance !

Fichiers standards

Aux premiers jours du CPC, Amsoft publiait les programmes sans protection. Dès lors, pas de problème, on sortait son copieur de fichiers (Transmat, Discologie, etc.) et voilà c'était fait. Quelques fois, il y avait des modifications à faire, mais pas grand chose : par exemple, un LOAD”fichier” avec un nom valide sur cassette mais pas sur disquette (avec des espaces, des points, etc.). Une autre particularité est l'emploi d'un fichier loader binaire. Lorsqu'un tel chargeur est exécuté sur disquette il y a un problème car le système remet aux indirections standard toutes les adresses système signifiant que l'Amsdos est inhibé… et utilise les routines cassette. Mais il n'était pas très difficile d'écrire un petit chargeur BASIC chargeant le binaire3) via les valeurs du header binaire (tous les copieurs de fichiers vous informent de ces valeurs) :

10 MEMORY commence-1:LOAD"chargeur.bin",commence
20 CALL execution

Blocs non standards principaux

Tiens, la bordure de l'écran a de belles couleurs pendant le chargement du jeu, la tête de lecture du lecteur de disquette bouge assez rapidement, c'est rigolo ! Oui, mais un peu plus difficile car ça signifie que notre programme est protégé avec une sauvegarde non standard. Que faire ? Un copieur de cassettes ne comprendra pas le bloc et un copieur de disquettes peut faire des faces en essayant de lire les secteurs du bloc.

Ce qu'il faut faire maintenant est un peu plus difficile. Connaissez-vous les instructions du Z80 ? Je suppose que oui, car vous êtes de fidèles lecteurs et pas mal d'articles de Quasar sont dédiés à l'assembleur Z80… sans blague, si vous ne connaissez pas le Z80, n'essayez pas de suivre car ce n'est pas facile à expliquer sans un peu de technique.

Démarrons un débugger pour examiner le loader. À moins qu'il ne soit encrypté (nous verrons cela plus tard), il est facile d'y trouver des lignes ressemblant à celles-ci :

        LD IX,adresse        ; C'est sûr !
        LD DE,longueur       ; C'est sûr !
        LD A,synchronisation ; Très probable
        SCF                  ; Très probable
        CALL routine         ; ou JP routine, ça dépend mais c'est sûr !

Grouarh ! Les registres peuvent être différents, mais la structure est toujours la même : c'est un appel à la routine qui charge le contenu du bloc non standard à l'adresse spécifiée.

Il y a deux façons de sauvegarder ces blocs. La première consiste à introduire, après le dernier “CALL routine”, un peu de code pour sauvegarder tout ce qui a été chargé. Mais ce n'est pas toujours une bonne idée car le loader peut charger un bloc sur un autre (par exemple : un jeu chargeant l'écran de présentation et plus tard l'écran du jeu lui-même). La deuxième solution est donc de faire une sauvegarde pour chaque bloc. Ceci a l'avantage de nous permettre d'employer des adresses différentes de celles d'origines (beaucoup de jeux chargent un bloc sur les appels système !).

Après avoir sauvegardé tous les blocs, il n'y a plus qu'à écrire un loader BASIC qui va charger les blocs devenus fichiers aux adresses correctes, et voilà ! Une légère difficulté est - à nouveau - la gestion des blocs effaçant les appels du système. Mais la solution est élémentaire : charger les blocs à un endroit différent et, après le chargement du dernier bloc, les déplacer aux bonnes adresses avec une routine assembleur. Pour ajouter le code assembleur dans le chargeur BASIC, des lignes de DATA et un peu de POKE et CALL sont suffisants. Voici un exemple :

Grouarh, aussi !

10 MEMORY adresse_minimale-1
20 LOAD"bloc.1",adresse.1:LOAD"bloc.2",adresse.2:, etc.
30 FOR h=&BF00 TO &BF0E
40 READ h$:POKE h,VAL("&"+h$)
50 NEXT
60 CALL &BF00
70 DATA F3,21,00,C0,11,00,A7,01,00,19,ED,B0,C3,00,01

Où le code binaire est :

        DI
        LD HL,&C000      ; Pas d'interruptions !
        LD DE,&A700      ; Origine
        LD BC,&1900      ; Longueur
        LDIR             ; Copier
        JP &0100         ; Sauter

Si le loader original fait plus de choses, tels que des appels aux blocs chargés, reproduisez-les dans le chargeur BASIC.

Blocs non standard secondaires

Nous avons déplombé un beau jeu et nous voulons jouer ; tout va bien… non ! Le jeu a besoin de charger les niveaux ! Il semble que notre déplombage ne soit pas complet : le programme utilise plus de blocs ; non seulement ceux contenant le programme lui-même, mais aussi ceux contenant les données.

Récupérez le débugger car il faut chercher l'endroit où le programme demande les blocs secondaires. Beaucoup de développeurs de logiciels utilisent toujours la même routine ; il est alors assez facile de la trouver dans le programme car c'est la même que celle du chargeur. C'est encore plus facile si le programme écrit des textes du type “Chargement bloc XX”, “Erreur de chargement”, etc. car il n'y a plus qu'à les trouver et à localiser la partie du programme les utilisant. En modifiant un peu le code, on peut alors sauvegarder les blocs secondaires de la même façon que les primaires.

Mais comment faire maintenant pour que le programme principal utilise les blocs secondaires ainsi sauvegardés ? C'est l'heure de patcher sérieusement le programme. Si le programme n'écrase jamais les appels système (c'est-à-dire les zones mémoire &0000-&003F et &A700-&BFFF4)), ceux-ci peuvent être utilisés pour patcher la partie du programme qui charge les blocs secondaires. Mais dans le cas où la mémoire système est détruite par le programme (le code, les données ou les buffers), l'emploi d'extensions de mémoire est parfois obligatoire.

Évidemment, le programme ne doit pas utiliser les extensions mémoire ou il doit au moins y laisser assez de place pour sauvegarder les appels système et la mémoire manipulée par le programme. Pourquoi ? Parce-que l'on doit récupérer l'état de la machine d'avant le chargement du bloc. Voici un exemple très simple :

Loader BASIC :

10 MEMORY minimum-1:LOAD"programme.1",adresse.1, etc.
20 FOR h=adresse.du.patch TO adresse.du.patch+longueur.du.patch
30 READ h$:POKE h,VAL$("&"+h$)
40 NEXT
50 DATA données.du.patch
60 FOR h=adresse.des.initialisations TO adresse.des.initialisations+longueur.des.initialisations
70 READ h$:POKE h,VAL$("&"+h$)
80 NEXT
90 DATA données.des.initialisation
100 CALL adresse.des.initialisations

Initialisations :

; ...initialisation préalables...
        DI             ; Pas d'interruptions !
        LD BC,&7FC4    ; Bloc 4 de mémoire 128k
        OUT (C),C      ; Sélection du bloc
        LD DE,&4000    ; Adresse de sauvegarde (1)
        LD HL,&0000    ; Origine
        LD BC,&40      ; Longueur
        LDIR           ; Sauvegarder
        LD HL,&A700    ; Origine
        LD BC,&1900    ; Longueur
        LDIR           ; Sauvegarder
        LD BC,&7FC0    ; État normal de la mémoire
; ...initialisations finales...
        JP exécution.du.programme

Patch :

; ...initialisations préalables...
        DI             ; Pas d'interruptions !
        LD BC,&7FC4    ; Bloc 4 de mémoire 128k
        OUT (C),C      ; Sélection du bloc
        LD DE,&5940    ; Adresse de sauvegarde (2)
        LD HL,&0000    ; Origine
        LD BC,&40      ; Longueur
        LDIR           ; Sauvegarder
        LD HL,&A700    ; Origine
        LD BC,&1900    ; Longueur
        LDIR           ; Sauvegarder
        LD (&7280),SP  ; Sauvegarde de la pile
        LD HL,&4000    ; Adresse de sauvegarde (1)
        LD DE,&0000    ; Origine
        LD BC,&40      ; Longueur
        LDIR           ; Récupération
        LD DE,&A700    ; Origine
        LD BC,&1900    ; Longueur
        LDIR           ; Récupération
        LD BC,&7FC0    ; État normal de la mémoire
        OUT (C),C      ; Sélection du bloc
; ...chargement du bloc...
        DI             ; Pas d'interruptions !
        LD BC,&7FC4    ; Bloc 4 de mémoire 128k
        OUT (C),C      ; Sélection du bloc
        LD HL,&5940    ; Adresse de sauvegarde (2)
        LD DE,&0000    ; Origine
        LD BC,&40      ; Longueur
        LDIR           ; Récupération
        LD DE,&A700    ; Origine
        LD BC,&1900    ; Longueur
        LDIR           ; Récupération
        LD SP,(&7280)  ; Récupération de la pile
        LD BC,&7FC0    ; État normal de la mémoire
        OUT (C),C      ; Sélection du bloc
; ...initialisations finales...
        RET/JP adresse ; Dépendant du chargeur original

Recommandations finales

Les chargeurs encryptés peuvent être très compliqués (Speedlock, Nemesis, etc.) mais la plupart se contentent de chaînes auto-modifiantes de XOR5). Un peu de traçage avec le débugger serait suffisant.

Pour utiliser la compression proposée dans mon article précédent, n'oubliez pas que le CPC a besoin d'un buffer de 2ko pour la lecture des données ! Les programmes de compression comme Cheese ou Imploder peuvent être dangereux car leur optimisation est assez agressive et votre code chargeur peut souffrir de leurs effets. Par contre, ils sont magnifiques pour des programmes en un seul fichier.

L'utilisation de l'espace &BE80-&BF7F pour votre code est traditionnelle, mais faites attention s'il faut écrire dessus ; alors, essayez &0040, une bonne alternative. Le RST 6 peut-être très utile pour des patchs d'espace limité : pokez en &0030 quelque chose du type LD BC,&7FC4:OUT (C),C:JP adresse.d.extension.du.patch, c'est très intéressant !

Essayez d'inclure votre pseudo et la date du déplombage dans le chargeur ; si possible faites une jolie intro. Mais ne modifiez pas les programmes : s'il faut les patcher, faites-le dans le loader. N'oubliez pas que notre but est la conservation des programmes. Surtout, ne changez pas les textes des programmes : ce serait idiot !

Conservez-moi !

1) Le loader ou chargeur pour les anglophobes
2) Un mot assez idiot, mais l'anglicisme “cracking” est encore pire
3) Chargeur, chargeant… ha ha !
4) ou &AC00-&BFFF pour un CPC sans ROM disque
5) Non, non, pas le shérif de l'espace…
 
dossier/deplombage.txt · Dernière modification: 2017/10/09 10:04 (édition externe)