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.
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