[Mercredi de BioWare] The ERF Format.

Répondre
Partager Rechercher
Je viens de voir ce truc, c'est en anglais, j'ai pas encore tout lu, je sais pas à quoi ça sert, je sais pas si c'est utile... (bon d'accord, c'est un erf editor, mais faut pas m'en demander plus)

Le petit texte de présentation :

Citation :
The Encapsulated Resource File (ERF) Format
[May 7, 2003] The Encapsulated Resource File (ERF) format is one of BioWare's methods of packing multiple files into a single file so that they may be treated as a single unit. In this regard, it is similar to .zip, .tar, or .rar. BioWare Aurora Toolset files that use the ERF format include the following: .erf, .hak, .mod, and .nwm. The example ERF Editor is an internal BioWare tool (Windows only).
Et le lien vers la page.
En fait Bio vient de faire un pas de géant : ils vont décrire l’un après l’autre leurs formats de fichier pour permettre au programmateurs amateurs et professionnels (il y en a un certain nombre, voire un nombre certain dans la communauté, par exemple Eyrdan qui est passé ici il y a quelque temps, et la plupart des openknights réguliers je pense) de faire des applications les manipulant, sans plus avoir à travailler dans le brouillard.
Ca, plus le fait qu’ils aient engagé Torlack , ça veut probablement dire beaucoup plus de flexibilité dans un futur relativement proche pour NWN. Certains des projets sont monstrueux, les openknights sont lancés sur un port du Toolset pour Linus et Mac OSX, il y a fort à parier qu’ils essaient de l’améliorer au passage.

Le format erf est aussi celui des fichiers .hak, .mod, .sav, .nwm, et j'en oubli peut-être. Donc tu peux voir l'éditeur en question comme une version rudimentaire (en fait je ne l'ai pas essayé, donc j'en sais rien) mais généraliste de nwhak.exe .
Bien pratique, ces petites spécifications, moi je suis en train de faire un module Nwn::Erf en Perl, et ça roule sans problème, grâce aux détails qu'ils ont donné, je devrais bientôt avoir fini toute la partie 'obtenir des informations sur le contenu' et 'extraction de ressources', puis je me collerai à la partie ajout, à priori, ça devrait pas être trop dur.
Bon là, je me heurte à un os ! Visiblement, le mode binaire pose problème lorsqu'il s'agit d'extraire des données comme les .nss mais le mode texte produit des erreurs sur les longueurs de ressources....

Bon bah on verra ça un autre jour hein... Je vous mets le script dès fois que quelqu'un trouve une solution (le reste marche parfaitement, c'est juste le résultat de l'extraction qui est zarbi...)

Le code actuel du module :
Code PHP:

package Nwn::Erf;
use 
bytes;


my %lang2ID = ( English => 0,
               
French => 1,
               
German => 2,
               
Italian => 3,
               
Spanish => 4,
               
Polish => 5,
               
Korean => 128,
               
'Chinese Traditional' => 129,
               
'Chinese Simplified' => 130,
               
'Japanese' => 131 );

my %ID2lang;
while( 
my$key$data) = each %lang2ID ) {
    
$ID2lang{$data} = $key ;
}

my %ID2ftype = ( => 'bmp',
                
=> 'tga',
                
=> 'wav',
                
=> 'plt',
                
=> 'ini',
                
10 => 'txt',
                
2002 => 'mdl',
                
2009 => 'nss',
                
2010 => 'ncs',
                
2012 => 'are',
                
2013 => 'set',
                
2014 => 'ifo',
                
2015 => 'bic',
                
2016 => 'wok',
                
2017 => '2da',
                
2022 => 'txi',
                
2023 => 'git',
                
2025 => 'uti',
                
2027 => 'utc',
                
2029 => 'dlg',
                
2030 => 'itp',
                
2032 => 'utt',
                
2033 => 'dds',
                
2035 => 'uts',
                
2036 => 'ltr',
                
2037 => 'gff',
                
2038 => 'fac',
                
2040 => 'ute',
                
2042 => 'utd',
                
2044 => 'utp',
                
2045 => 'dft',
                
2046 => 'gic',
                
2047 => 'gui',
                
2051 => 'utm',
                
2052 => 'dwk',
                
2053 => 'pwk',
                
2056 => 'jrl',
                
2058 => 'utw',
                
2060 => 'ssf',
                
2065 => 'ptm',
                
2066 => 'ptt',);

my %ftype2ID;
while( 
my$key$data) = each %ID2ftype ) {
    
$ftype2ID{$data} = $key ;
}

###### Lecture et Extraction ######
###### Read & Extract ######


sub get_from_file {
    
my $caller shift;
    
my $class ref($caller) || $caller;
    
my $name shift;
    
my $self = {name => $name
                
numres => 0};
    
my $mode = -e $name "+<:raw" "+>:raw";
    
open $self->{file}, $mode$name or die "Impossible de créer l'objet ERF à partir de $name : $! \n";
    return 
bless $self$class;
}


sub get_header {
    
my $self shift;
    if ( 
not exists $self->{header} ) {
        
my $packedstring;
        
read $self->{file}, $packedstring44;
        
my (@header) = ( unpack"A4A4LLLLLLLLL",  $packedstring), );
        
my %header = (  FileType => $header[0],
                        
Version => $header[1],
                        
LanguageCount => $header[2],
                        
LocalizedStringSize => $header[3],
                        
EntryCount => $header[4],
                        
OffsetToLocalizedString => $header[5],
                        
OffsetToKeyList => $header[6],
                        
OffsetToResourceList => $header[7],
                        
BuildYear => $header[8],
                        
BuildDay => $header[9],
                        
DescriptionStrRef => $header[10],
                        
Reserved => $header[11],);
        
read $self->{file}, $packedstring$header[5] - 44;
        
$header{Reserved} = $packedstring;
        
$self->{header} = \%header;
    }
    return %{
$self->{header}};
}

sub file {
    
my $self shift;
    
my $name shift;
    if( @
) {
        
open $self->{file}, "<"$_[0] or die "Impossible de créer l'objet ERF à partir de $_[0] : $! \n";
        
$self->{file} = $self->{file};
    }
    return 
$self->{file};
}

sub name {
    
my $self shift;
    if( @
) {
        
my $name shift;
        
rename $self->{name}, $name or die "Impossible de renommer $self->{name} en $name : $!";
        
$self->{name} = $name
    
}
    return 
$self->{name};
}

sub get_description {
    
my $self shift;
    
my $lang shift if @_;
    if(
not exists $self->{description}) {
        
my %header $self->get_header;
        
my %description;
        
my ($ID$size$desc$spack);
        
seek $self->{file}, $header{OffsetToLocalizedString}, 0;
        if( 
$header{LanguageCount} ) {
            for(
1..$header{LanguageCount}) {
                
read $self->{file}, $spack8;
                (
$ID$size) = unpack"LL"$spack );
                
read $self->{file}, $desc$size;
                
$description{$ID2lang{$ID}} = $desc;
            }
        }
        
$self->{description} = \%description;
    }
    return %{
$self->{description}} if wantarray;
    return 
$self->{description}{$lang} if exists $self->{description}{$lang};
}

sub resources {
    
my $self shift;
    
    
my %header $self->get_header;
    if(
not exists $self->{list}) {
        
my @list;
        if(
$header{EntryCount}) {
            
            
seek $self->{file}, $header{OffsetToKeyList}, 0;
            
my ($resref$restype$unused$spack);
            for( 
1..$header{EntryCount}) {
                
read $self->{file}, $spack24;
                (
$resref$restype$unused) = unpack"A16xxxxSS"$spack );
                
$list[$_ 1] = { ResRef => $resref,
                                  
ResType => $restype,
                                  
Unused => $unused,};
            }
            
            
seek $self->{file}, $header{OffsetToResourceList}, 0;
            
my ($offset$size);
            for( 
1..$header{EntryCount}) {
                
read $self->{file}, $spack8;
                (
$offset$size) = unpack"LL"$spack );
                
$list[$_ 1]->{OffsetToResource} = $offset;
                
$list[$_ 1]->{ResourceSize} = $size;
            }
        }
        
$self->{list} = \@list;
    }
    if(
wantarray) {
        
$self->{numres} = 0;
        return @{
$self->{list}};
    }
    if(
$self->{numres} == $header{EntryCount}) {
        
$self->{numres} = 0;
        return 
undef;
    }
    return 
$self->{list}[$self->{numres}]{ResRef} . '.' $ID2ftype{$self->{list}[$self->{numres}++]{ResType}};
}

sub extract {
    
my $self shift;
    
my $path pop;
    
$path .= '/' unless $path =~ m{(?:/|\\)$};
    
my @list = $self->resources;
    
my %header $self->get_header;
    if(@
_) {
        
my $tocopy;
        
my $toextract;
        while(@
_){
            
$toextract shift;
            
$toextract qr/$toextract/;
            for(
0..($header{EntryCount}-1)) {
                
my $resref $list[$_]{ResRef} . '.' $ID2ftype{$list[$_]{ResType}};
                if( 
$resref =~ /$toextract/ ) {
                    
seek $self->{file}, $list[$_]{OffsetToResource}, 0;
                    
open EXTRACT'>:raw'$path $resref or die "Impossible de créer ${path}${resref} : $!";
                    
read $self->{file}, $tocopy$list[$_]{ResourceSize};
                    print 
EXTRACT $tocopy;
                }
            }
        }
    }
    else {
        for(
0..($header{EntryCount}-1)) {
            
my $resref $list[$_]{ResRef} . '.' $ID2ftype{$list[$_]{ResType}};
            
seek $self->{file}, $list[$_]{OffsetToResource}, 0;
            
open EXTRACT'>:raw'$path $resref or die "Impossible de créer ${path}${resref} : $!";
            
read $self->{file}, $tocopy$list[$_]{ResourceSize};
            print 
EXTRACT $tocopy;
        }
    }
}

1

Un exemple d'application :
Code PHP:

#!/usr/bin/perl

use Nwn::Erf;
my $ERF Nwn::Erf->get_from_file"c:/jeux/neverwinternights/nwn/modules/DEMO - The Cat Lady.mod" );

my %header $ERF->get_header;
while( 
my ($key$data ) = each %header) {
    print 
"$key : $data \n";
}
my %description $ERF->get_description;
while( 
my ($key$data ) = each %description) {
    print 
"$key : $data \n";
}
while( 
my $resref $ERF->resources ) {
    print 
$resref "\n";
}

print 
scalar $ERF->get_description'French');
$ERF->extract"c:/jeux/neverwinternights/nwn/magicerf/" ); 
Bon, voilà, si quelqu'un a une idée ?
Bon inutile de répondre, finalement j'ai trouvé....

Je lisais en mode binaire mais j'écrivais en mode texte

:bouffon: :bouffon:

Bon, je vous fournirai la version finale.
(Avec peut-être une interface)
Hein, A quoi ça sert ?
Ben....à rien.

Bon, OK, sérieusement ? Et bien ce module offrira des fonctions pratiques pour manipuler tous les fichiers de type ERF, c'est à dire les .erf, .mod, .hak, .nwm, .sav. On pourra très facilement, avec un tout petit programme Perl, extraire ou ajouter des ressources automatiquement dans ce type de fichier, y compris sous Linux.
Ca permet d'automatiser les gros traitements sous Windows (exemple, quand on veut faire une modif simple de tous les scripts de sort) et l'intégration dans un module. Sous Linux, ça peut constituer une base pratique pour un mini-toolset (plutôt pour un packer-unpacker de module).

Et pis ça m'amuse pas mal aussi .

Bon, par ailleurs, tout le monde ne sait pas programmer en C(++), et le Perl est très facile à apprendre, et très puissant, c'est un vrai plaisir de travailler avec après avoir subi un C (ou un NWScript) fastidieux.

Je le conseille d'ailleurs fortement (pour obtenir une distribution win32, on peut aller sur ce site, sous Linux, perl.com fera l'affaire).

Exemple de programme simple :
Code PHP:

#!/usr/bin/perl

use Nwn::Erf;

my $ERF Nwn::Erf->get_from_file"c:/jeux/neverwinternights/nwn/modules/DEMO - The Cat Lady.mod" );

$ERF->extract'\\.nss$' "c:/jeux/neverwinternights/nwn/catladyscript/" ); 
Extrait tous les scripts (du moins leurs codes sources) du module DEMO- The Cat Lady, pour traitement, ou transmission ultérieure.
Aaaaah, super

Ca m'a l'air très sympa tout ça

Mais là je me vois contraint de faire une requête perso : par pitié Jedaï, fait en sorte qu'on puisse supprimer plusieurs trucs d'un module d'un coup

Parceque la suppression à la main de toute une palette de créatures custom, je sais pas si t'as déja essayé, mais c'est difficile, mentalement

Voilà
Oui, ce sera très facile de supprimer beaucoup de chose d'un coup, et pourvu qu'en plus on connaisse un peu les regex (ou regular expressions, ou expression rationnelles, ou expressions régulières), on pourra facilement supprimer un très grand nombre de fichiers dont le nom se ressemble, ou qui sont du même type, exemple : pour détruire toutes les créatures d'une palette custom, il suffira de faire :
Code PHP:

#Indispensable avant ajout ou suppression
$ERF-> open_modif_session;

$ERF-> deleteqr/.utc$/ );
#On peut mettre d'autres modifications ici :-)

#valide toutes les modifications précédentes
$ERF -> close_modif_session
Evidemment, j'ai l'intention de proposer une interface pour ce genre de chose, probablement d'abord une interface en ligne de commande, puis une graphique (mais bon pas tout de suite, parce que là j'ai pas le temps ).

Pour l'instant, la vitesse de ce script est assez grande, il met environ moitié moins de temps que l'extracteur d'erf fourni par Bioware (environ 30 sec pour extraire 3380 fichiers = 16,3 Mo (sur mon P3 500 ) testé sur WW1).

Je proposerais sans doute aussi de pouvoir utiliser les jokers standard du shell à la place des expressions régulière (enfin on verra ).
Une remarque tout de même, il y a pas mal de trucs dans les modules qui ne sont pas dans des fichiers séparés mais des entrées dans des GFF (oui je suis snob , GFF, nous a révélé Bio avant hier, c'est le format des .itp etc) . Par exemple les objets eux même si je me souviens bien (ie != blueprints) cf "creaturepalcus" ou quelque chose comme ça je crois.
Sinon le PEarl ça à l'air sympa. Juste, si j'ai bien vu, c'est pas typé? (j'ai regardé vite fait étant donné que je ne comprend pas grand chose à la syntaxe) , les langages impératifs non typés ça me fait un peu peur . Tu n'aurais pas l'adresse d'un bon tutoriel par hasard Jedaï?

Moi j'aime bien le C, c'est rigolo les pointeurs:

Code PHP:

*((PDWORD) (pDestTemp TESTROFF)) = *((PDWORD) (pCustCurrent TESTROFF)) +  dwStringSectionSize
PERL = Practical Extraction & Report Language (Langage pratique d'extraction (de donnée) et de rapport)

Pour l'adresse d'un petit tutorial pas mal fait, tu as :
http://www.lhullier.org/
(Le mieux ça reste encore le Camel book chez O'Reilly)
La doc traduite en français (pas complète) est disponible là :
http://www.enstimac.fr/Perl/

Et la doc en anglais complète est dans la distribution de ActiveState.

Pour les itp, je sais bien, mais je ferais sûrement un module pour eux aussi, ce qui devrait déjà me donner le support de 80% des fichiers utilisés dans nwn.

Pour un bon éditeur d'ITP actuellement, il faut prendre LETO, il est vraiment bien.

A noter que plus les conventions pour nommer les scripts/blueprint, etc sont claires, plus ce genre de truc est pratique, puisqu'on peut ainsi cibler les interventions du logiciel de façon plus précise.
Citation :
Provient du message de Taern
Mais là je me vois contraint de faire une requête perso : par pitié Jedaï, fait en sorte qu'on puisse supprimer plusieurs trucs d'un module d'un coup

Parceque la suppression à la main de toute une palette de créatures custom, je sais pas si t'as déja essayé, mais c'est difficile, mentalement
Bin ! Ne soyons pas snobs, l'explorer windaube est très bien, pour ça, non ? Quand ton mod est ouvert par le toolset, le fichier est décompressé dans un sous-répertoire "temp0" du répertoire "modules". Tu chopes les fichiers .utc, les fichiers de créatures nommés par "resref.utc", hop tu deletes et le problème est réglé. (Ah, non, faut refresh depuis la 1.28, sinon ça buuuug )

Sinon, ça doit bien pouvoir permettre de faire de la fusion de .erf, tout ça, non ? Quelqu'un posait la question récemment, justement.
Répondre

Connectés sur ce fil

 
1 connecté (0 membre et 1 invité) Afficher la liste détaillée des connectés