Auteur

Auteur: Faiseur

jeudi 22 juillet 2010

Les algorithmes de compression - partie 3




Tutoriel réalisé par Faiseur

Niveau: [ Intermédiaire ]





INTRODUCTION


Dans cette troisième partie du tutoriel est expliquée une méthode pour récupérer une ressource en mémoire, la décompresser puis l'utiliser. Dans l'exemple qui est proposé la ressource traitée est un fichier musical au format .xm, qui sera envoyé dans un module (en l'occurence uFMOD) permettant de le faire jouer.

L'avantage de cette méthode est qu'elle permet d'optimiser au maximum (ou presque) la taille de l'exécutable en diminuant la taille d'une ressource. Elle rend également les données d'une ressource illisible puisque celle-ci est compressée. De plus le procédé est propre et discret car tout se fait en mémoire, il n'y a aucun accès disque.


Synopsis:

Ressource compressée --> extraction en mémoire --> décompression en mémoire --> envoi vers le module uFMOD




RECUPERATION D'UNE RESSOURCE


La procédure d'extraction de la ressource pour la convertir en une mémoire utilisable s'opère en se servant des 4 API suivantes:

FindResource

SizeofResource

LoadResource

LockResource




FindResource permet de récupérer le handle (un identifiant unique) d'une ressource placée dans l'exécutable. Toutefois ce handle ne pointe pas sur une mémoire que nous pouvons directement utiliser. Windows ne simplifie pas la travail à ce point.

SizeofResource permet de récupérer la taille, en bytes, de la ressource, à partir de son handle (que FindResource nous a retourné). Cette taille est une donnée précieuse lorsque nous souhaitons travailler avec une ressource. Par exemple pour l'écrire au complet dans un fichier. Pour rappel les fonctions "standard" de calcul de taille de buffer ne permettent pas de mesurer la taille exacte d'une donnée en mémoire s'il s'agit de données binaires. En effet, au premier "0" trouvé le calcul de longueur de chaîne s'arrête. Or un fichier binaire en contient un certain nombre.

LoadResource retrouve un nouveau handle (que l'on peut nommer 'handle data') d'une ressource à partir du handle retourné par FindRessource. Ce 'handle data' peut être utilisé pour obtenir un pointeur vers le premier byte de la ressource en mémoire.

LockResource retrouve le pointeur d'une ressource en mémoire à partir du 'handle data'. C'est enfin ce qu'il nous faut. Ce pointeur ciblera le premier byte de la ressource récupérée en mémoire.

Voici notre procédure d'extraction:

Code:

ExtractResourceUnPackMem proc hinstance,Number,ResType:DWORD
   local hResource,hResourceSize:dword
   LOCAL szCurrentPath[261],szExtractPath[261]:BYTE

   invoke FindResource, hinstance,Number,ResType
   .if eax != 0
      mov hResource, eax
      invoke SizeofResource, hinstance, hResource
      .if eax != 0
         mov hResourceSize, eax
         invoke LoadResource, hinstance, hResource
         .if eax != 0
            mov hResource,rv(LockResource, eax)
             ; hResource pointe sur l'adresse en mémoire, hResourceSize sa taille
            ret
         .endif
      .endif
   .endif
   ret
ExtractResourceUnPackMem endp



Arrivés au point névralgique...

Code:
mov hResource,rv(LockResource, eax)


...nous aurons:

- hResource pointe sur l'adresse en mémoire que nous pouvons utiliser comme un buffer normal

- hResourceSize nous en donne sa taille




CHOIX DE LA LIBRAIRIE DE COMPRESSION



Il nous faut à présent décompresser la donnée en mémoire. Je propose, dans cet exemple, la librairie Aplib. Mon critère de choix est essentiellement qu'il n'y a pas besoin de dll embarquée et que la compression est bonne. Si l'on se réfère à la 2ème partie du tutoriel un autre choix intéressant aurait pu être Jcalg1 vu qu'il s'agit ici uniquement de décompression. On peut ainsi compresser plus fortement que Aplib, la durée de compression n'étant pas un critère.

J'ai donc préalablement compressé le fichier .xm avec la librairie Aplib puis l'ai placée en tant que ressource. Vous trouverez dans l'archive volumineuse de ce tutoriel le logiciel Appack, qui permet de compresser un fichier avec cette librairie.

A présent comment décompresser en mémoire ? C'est plutôt simple. Il y a des retouches à faire avec notre procédure de décompression Aplib pour se servir directement de l'adresse mémoire et de sa taille. Voici la procédure au complet. J'ai préféré conserver deux procédures distinctes pour plus de clarté. Notre première procédure, 'ExtractResourceUnPackMem', inclus l'extraction de la ressource en une adresse mémoire utilisable (ce que nous avons vu plus haut). Elle appelle ensuite une autre procédure, 'APLIBUnpackMem', pour décompresser en mémoire avec Aplib. 'ExtractResourceUnPackMem' retourne ensuite le résultat.



Code:

ExtractResourceUnPackMem proc hinstance,Number,ResType:DWORD
   local hResource,hResourceSize:dword
   LOCAL szCurrentPath[261],szExtractPath[261]:BYTE

   invoke FindResource, hinstance,Number,ResType
   .if eax != 0
      mov hResource, eax
      invoke SizeofResource, hinstance, hResource
      .if eax != 0
         mov hResourceSize, eax
         invoke LoadResource, hinstance, hResource
         .if eax != 0
            mov hResource,rv(LockResource, eax)
             fn APLIBUnpackMem,hResource,hResourceSize
            ret
         .endif
      .endif
   .endif
   ret
ExtractResourceUnPackMem endp


Code:

; Aplib, retour 0 si erreur / la mémoire est placée en eax, sa taille en ecx / utiliser la macro free pour la libérer
APLIBUnpackMem proc Source,SourceSize:DWORD
    LOCAL hFile,ln,br,dsize,dest$:DWORD

    cmp Source[0], 0
    jne @F
    mov eax, 0
    ret
    @@:
    mov dsize,rv(aPsafe_get_orig_size,Source)

    test eax,eax
    jnz @F
    mov eax,0 ; non compressé par aPPack"
    ret
    @@:

    mov dest$,alloc(dsize)
    invoke aPsafe_depack,Source,SourceSize,dest$,dsize
     
    mov eax,dest$
    mov ecx,dsize
    ret
APLIBUnpackMem endp



Les points importants suivants sont à noter:

- La procédure 'ExtractResourceUnPackMem' retourne '0' si erreur

- La ressource placée en mémoire est retournée dans le registre EAX

- La taille de la ressource placée en mémoire est retournée dans le registre ECX

- Il sera nécessaire de libérer la mémoire retournée dans EAX après son utilisation. Utilisez la macro free pour libérer la mémoire, ou l'API GlobalFree.





PARAMETRES D'APPEL


'ExtractResourceUnPackMem' nécessite trois paramètres. Afin d'être le plus polyvalent possible les paramètres à envoyer concernent tous les cas de figure:

- paramètre 1: le handle du module exécutable contenant la ressource à extraire, par défaut ce sera hInstance. Toutefois ce paramètre peut avoir une valeur NULL si vous ne l'avez pas.

- paramètre 2: l'identifiant de la ressource (ou ressource ID)

- paramètre 3: le type de la ressource. Une ressource compressée doit normalement être placée en tant que RCDATA.



Voici un exemple de syntaxe d'appel:

Code:
invoke ExtractResourceUnPackMem,hInstance,10002,RT_RCDATA





uFMOD


Pour conclure cet exemple nous allons passer la ressource au format .xm dans un module adapté pour l'interpréter: uFMOD.

La syntaxe d'appel est la suivante:

Code:
invoke uFMOD_PlaySong, [module .xm], [taille du module], [options]    




Vous allez trouver parmi les sources fournies dans l'archive de ces tutoriels l'exécutable "LoadandUnpackMem" (difficile de mieux simplifier qu'en anglais). Cet exemple correspond à ce qui est décrit dans ce tutoriel: il charge une ressource de musique (préalablement compressée) au format .xm, la décompresse en mémoire puis la fait jouer grâce à la librairie uFMOD.

J'en profite pour faire connaître cette excellente librairie qui est notable pour plusieurs raisons:

- Elle fonctionne très bien

- Elle est codée en assembleur et donc aisée à intégrer dans vos applications en langage assembleur

- Elle est légère et, selon leurs auteurs, il s'agit certainement de la plus petite librairie de ce type


Vous trouverez inclus dans l'exemple la librairie uFMOD avec ses includes en version Masm:

Code:

ufmod.inc
ufmod.lib


Le fichier 'ufmod.inc' inclus un descriptif des fonctions de la librairies, que vous trouverez également sur le site officiel.

Attention, il est nécessaire d'ajouter la librairie 'winmm' pour compiler ce module dans vos sources.

Comme vous le verrez cette librairie peut charger un module .xm depuis une ressource, un fichier sur le disque ou encore en mémoire. C'est évidemment cette troisième option que j'ai sélectionné. Dans ce cas de figure uFMOD demande l'adresse de la mémoire et sa taille, pile poil ce que nous avons récupéré après avoir extrait la ressource.


Source de l'exemple:

.asm
Code:

include SqueletteGUI.inc

.code

APLIBUnpackMem proc Source,SourceSize:DWORD ; Aplib, retour 0 si erreur / la mémoire est placée en eax, sa taille en ecx / utiliser la macro free pour la libérer
    LOCAL hFile,ln,br,dsize,dest$:DWORD

    cmp Source[0], 0
    jne @F
    mov eax, 0
    ret
  @@:
    mov dsize,rv(aPsafe_get_orig_size,Source)

    test eax,eax
    jnz @F
   mov eax,0 ; non compressé par aPlib 
   ret
  @@:

    mov dest$,alloc(dsize)
    invoke aPsafe_depack,Source,SourceSize,dest$,dsize
   
   mov eax,dest$
   mov ecx,dsize
    ret
APLIBUnpackMem endp

ExtractResourceUnPackMem proc hinstance,Number,ResType:DWORD
   local hResource,hResourceSize:dword
   LOCAL szCurrentPath[261],szExtractPath[261]:BYTE

   invoke FindResource, hinstance,Number,ResType
   .if eax != 0
      mov hResource, eax
      invoke SizeofResource, hinstance, hResource
      .if eax != 0
         mov hResourceSize, eax
         invoke LoadResource, hinstance, hResource
         .if eax != 0
            mov hResource,rv(LockResource, eax)
            fn APLIBUnpackMem,hResource,hResourceSize 
            ret   
         .endif
      .endif 
   .endif
   ret
ExtractResourceUnPackMem endp

DlgProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM


   mov      eax,uMsg
   .if eax==WM_INITDIALOG
      fn ExtractResourceUnPackMem,hInstance,100,RT_RCDATA
      mov Music,eax
      push ecx
      invoke SetDlgItemInt,hWin,1003,ecx,FALSE
      pop ecx
      invoke uFMOD_PlaySong,Music,ecx,XM_MEMORY 

   .elseif eax==WM_COMMAND
      mov      edx,wParam
      movzx   eax,dx
      shr      edx,16
      .if eax==IDC_BTN1
         invoke SendMessage,hWin,WM_CLOSE,NULL,NULL
      .endif

   .elseif eax==WM_CLOSE
      free Music
      invoke EndDialog,hWin,0
   .else
      mov   eax,FALSE
      ret
   .endif
   mov   eax,TRUE
   ret

DlgProc endp

start:
   mov hInstance,rv(GetModuleHandle,0)
   invoke InitCommonControls
   invoke DialogBoxParam,hInstance,IDD_DIALOG1,NULL,addr DlgProc,NULL
   invoke ExitProcess,0
end start



.inc
Code:

.386
.model flat, stdcall
option casemap :none

include windows.inc
include \masm32\macros\macros.asm

include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib
include comctl32.inc
includelib Comctl32.lib
include winmm.inc ; nécessaire pour ufmod
includelib winmm.lib

include Lib/aplib.inc
includelib Lib/aplib.lib

include lib/ufmodapi.inc
includelib lib/ufmod.lib

.const

IDD_DIALOG1         equ 1000
IDC_BTN1         equ 1001

.data?
Music            dd ?
hInstance         dd ?





COMPLEMENTS


Vous trouverez dans les archives tout ce dont vous aurez besoin pour vos applications concernant ce qui a été traité dans les trois parties:

- Les librairies des différentes compressions abordées dans la deuxième partie (Zlib, Aplib,Jcalg1) avec leurs includes pour Masm

- Une sauvegarde des documentations concernant l'utilisation de Aplib et Jcalg1

- La librairie ntdll avec ses includes pour Masm de manière à ne pas avoir besoin de récupérer l'adresse des fonctions NT pour utiliser la compression Windows

- NTComp, l'exemple instructif de MichaelW (source code inclus) concernant la méthode de compression de Windows

- Mon exemple "LoadandUnpackMem" avec sources, qui inclus les procédures abordées dans ce tutoriel: l'extraction d'une ressource en mémoire, sa décompression puis son utilisation en mémoire. Vous trouverez dans les librairies de cet exemple également la librairie 'UfMod' dans sa dernière version à date, avec ses includes pour Masm.

- Le logiciel Appack mis à jour par mes soins. Il utilise la version 1.01 de Aplib. Ce programme est un exécutable permettant de compresser et/ou décompresser un fichier en utilisant la librairie Aplib.


L'archive regroupant ce qui a été passé en revue dans les trois parties de ce tutoriel se télécharge ici.

0 commentaires:

Enregistrer un commentaire