el Guille, la Web del Visual Basic, C#, .NET y más...

Extraer una imagen de un fichero PBP

 
Publicado el 31/Jul/2008
Actualizado el 31/Jul/2008
Autor: Guillermo 'guille' Som

Aquí te muestro cómo leer la cabecera de un fichero con formato PBP (PSP de Sony) y extraer la imagen indicada por ICON0.PNG. De forma fácil puedes extraer el resto del contenido. También explico cómo convertir un valor guardado como little endian a Int32.



 

La batallita del agüelo:

No me voy a enrollar demasiado explicando qué hace este código, pero te pongo en situación:

En mis foros, preguntaron cómo "Leer secuencia de Bytes en un archivo binario" y se refería a extraer una imagen de un fichero con extensión PBP.

Por lo que he visto en la "red", ese tipo de fichero es el usado por las PSP de Sony (Play Station) y entre otras cosas tiene una imagen en formato PNG, el código que te muestro aquí sirve para extraer esa imagen y guardarla como un fichero independiente.

El formato de los ficheros PBP y cómo extraer el contenido

Los ficheros PBP tienen una cabecera con la información que contiene. Esa cabecera es de 40 bytes y tiene el siguiente formato/información:

Offset Contents Remarks
0-3 File type/descriptor Always a null byte followed by "PSP".
4-7 PBP Version (?) Always 00 00 01 00.
8-11 Offset of PARAM.SFO  
12-15 Offset of ICON0.PNG  
16-19 Offset of ICON1.PMF  
20-23 Offset of PIC0.PNG  
24-27 Offset of PIC1.PNG  
28-31 Offset of SND0.AT3  
32-35 Offset of DATA.PSP  
36-39 Offset of DATA.PSAR  

Según indica esta página: http://www.pdc.me.uk/pbpdocs/ los "offset" son valores enteros sin signo de 32 bits que están almacenados como "little endian", que para que nos entendamos, es como si los valores estuvieran al revés. Por ejemplo, si el valor del offset de ICON0.PNG fuese 448 se almacenaría como 4 bytes que en valor hexadecimal tendrían esta secuencia dentro del fichero: C0 01 00 00 (pero sin espacios). Ese valor convertido en formato "normal" sería: 00 00 01 C0 que corresponde al valor decimal 448.

Ese offset representa la posición en que se inicia la imagen que corresponde a ICON0.PNG.

La pregunta es ¿y cuánto mide esa imagen?

Antes de responderte y para que lo entiendas (y te sirva para extraer el resto del contenido), ese offset indica dónde empieza y la longitud que tiene viene dada por la posición del siguiente "fichero" menos ese offset.

En el fichero PBP lo siguiente a ICON0.PNG es ICON1.PMF (que no se lo que es, no tengo una PSP) y en el fichero EBOOT.PBP que he usado para probar (bajado de una página, ya te digo que no tengo una PSP), la posición de inicio (el offset) de ICON1.PMF es: 5366 (en hexadecimal: 00 00 14 F6), por tanto, la longitud de ICON0.PNG es: 5366 - 448 = 4918.

Con el resto de cosas habría que hacer lo mismo, hasta el último (DATA.PSAR), que al no haber nada después, habría que considerar que el offset siguiente es la longitud total del fichero.

Por lo que se ve, no todas las "cosas" que tiene un fichero PSP está dentro, por tanto, es posible que varios de esos offsets tengan el mismo valor, por tanto la longitud será cero, es decir, que no hay nada. Esto ocurre con el fichero ese que me he bajado.

Una clase para "casi" manipular los ficheros PBP

Para simplificar el acceso al contenido de los ficheros PBP he creado una clase que lee la cabecera y asigna los valores de unos campos públicos (que representan a cada uno de los valores mostrados en la tabla anterior).

Esa clase también define unos métodos estáticos (compartidos que lo llamamos los que usamos Visual Basic) que sirve para convertir un offset en un valor hexadecimal y en un valor entero. Recuerda que esos valores (los offset) están guardados como 4 bytes y en little endian, por tanto esos métodos tienen en cuenta ese "detalle" y hacen bien su trabajo.

La función Offset2Hex convierte el offset indicado en un valor hexadecimal que después se puede convertir en un valor entero de 32 bits. En el código comentado puedes ver una forma más fácil de saber qué es lo que hace ese código, que en el fondo es bien simple: toma los valores de cada uno de los bytes del offset, lo convierte en una cadena hexadecimal, y después le da la vuelta (little endian lo almacena de atrás hacia adelante) y eso es lo que devuelve.

Este es el código para Visual Basic:

Public Shared Function Offset2Hex(ByVal offset As String) As String
    ' Si el valor es C0010000
    ' El valor devuelto será 000001C0

    If Len(offset) <> 4 Then
        Return "00000000"
    End If


    ' De forma simple para que se entienda mejor

    'Dim h1, h2, h3, h4 As String

    'h1 = Asc(Mid(offset, 1, 1)).ToString("x2")
    'h2 = Asc(Mid(offset, 2, 1)).ToString("x2")
    'h3 = Asc(Mid(offset, 3, 1)).ToString("x2")
    'h4 = Asc(Mid(offset, 4, 1)).ToString("x2")

    'Return h4 & h3 & h2 & h1

    ' De forma no tan simple, pero igual de efectiva

    Dim hx As String = ""
    For i As Integer = 0 To offset.Length - 1
        hx = Asc(Mid(offset, i + 1, 1)).ToString("x2") & hx
    Next

    Return hx
End Function

Una vez que tenemos el valor en hexadecimal, lo convertimos a un entero de 32 bits (Integer en VB, int en C#), esto lo hacemos con la función Offset2Integer de la que te muestro el código de Visual Basic:

Public Shared Function Offset2Integer(ByVal offset As String) As Integer
    Dim sHex As String = Offset2Hex(offset)
    Return CInt(Val("&H" & sHex))
End Function

Y para el que quiera tenerlo todo como una sola función, he definido el método LittleEndianToInt32 que recibe un offset como parámetro y devuelve directamente el valor entero:

''' <summary>
''' Para los valores guardados como little endian
''' (32-bit unsigned little endian)
''' </summary>
Public Shared Function LittleEndianToInt32(ByVal offset As String) As Integer
    Return Offset2Integer(Offset2Hex(offset))
End Function

 

Para leer la cabecera del fichero PBP y asignar los campos de la clase lo hago desde un método al que se llama desde el constructor de la clase y al que se le pasa el nombre del fichero PBP que la clase usará (esta clase solo permite crear nuevos objetos si se indica el fichero PBP a usar).

Este método va leyendo de 4 en 4 bytes y va asignando los campos de la clase:

''' <summary>
''' Leer la cabecera del fichero PBP
''' </summary>
Private Sub pLeerInfoPBP(ByVal fic As String)
    m_Fic = fic

    Dim by() As Byte

    Dim fst As New FileStream(fic, _
                              FileMode.Open, _
                              FileAccess.Read, _
                              FileShare.Read)
    Using br As New BinaryReader(fst, Encoding.Default)
        With Me
            by = br.ReadBytes(4)
            .file_PBP = Encoding.Default.GetString(by)
            by = br.ReadBytes(4)
            .PBPversion = Encoding.Default.GetString(by)
            by = br.ReadBytes(4)
            .PARAM_SFO = Encoding.Default.GetString(by)
            by = br.ReadBytes(4)
            .ICON0_PNG = Encoding.Default.GetString(by)
            by = br.ReadBytes(4)
            .ICON1_PMF = Encoding.Default.GetString(by)
            by = br.ReadBytes(4)
            .PIC0_PNG = Encoding.Default.GetString(by)
            by = br.ReadBytes(4)
            .PIC1_PNG = Encoding.Default.GetString(by)
            ' idem con el resto
        End With

    End Using
    fst.Close()
End Sub

No he asignado todos los valores, ya que lo he hecho para probar lo de extraer ICON0.PNG, por tanto, si quieres tener toda la información debes seguir añadiendo el resto de lectura/asignaciones. Pero no debería ser complicado, al menos al ver cómo está hecho el resto de asignaciones.

Extraer el fichero ICON0.PNG

Para extraer el fichero, he creado un método que se apoya en otros dos que indican en qué posición empieza y qué longitud tiene.

Public Function PosICON0_PNG() As Integer
    Return Offset2Integer(ICON0_PNG)
End Function

Public Function LenICON0_PNG() As Integer
    Dim pic0 As Integer = Offset2Integer(ICON0_PNG)
    Dim pic1 As Integer = Offset2Integer(ICON1_PMF)

    Return pic1 - pic0
End Function

Como puedes ver en LenICON0_PNG para calcular la longitud se mira en qué offset empieza el siguiente dato y se le resta el valor en el que empieza el que queremos extraer (como ya te comenté antes).

Y ahora solo queda extraer esa información del fichero y guardarlo como un fichero .PNG, y eso es lo que hace la función GuardarICON0_PNG:

Public Sub GuardarICON0_PNG()
    Dim pos As Integer = PosICON0_PNG()
    Dim longitud As Integer = LenICON0_PNG()

    Dim fic As String = m_Fic
    Dim img As String = Path.Combine(Path.GetDirectoryName(fic), _
                                     "ICON0.PNG")
    Dim by() As Byte

    Using fst As New FileStream(fic, _
                                FileMode.Open, _
                                FileAccess.Read, _
                                FileShare.Read)

        fst.Seek(pos, SeekOrigin.Begin)
        ReDim by(0 To longitud - 1)
        fst.Read(by, 0, longitud)

        Using bw As New FileStream(img, _
                                   FileMode.OpenOrCreate, _
                                   FileAccess.Write, _
                                   FileShare.Read)
            bw.Write(by, 0, by.Length)
            bw.Close()
        End Using

        fst.Close()
    End Using

End Sub

 

Ejemplo completo para leer la info y extraer la imagen de un fichero PBP

Eso es lo que contiene el proyecto que está en el ZIP, ese proyecto está hecho en Visual Basic 2008, pero es totalmente compatible con Visual Basic 2005 (solo tendrías que crear un nuevo proyecto de VB2005 y añadir el formulario y la clase CabeceraPBP).

No sé si lo convertiré a C#, ya que uso funciones propias de Visual Basic y ahora con el calor que hace me da pereza convertirlo, además de que la funcionalidad está en la clase CabeceraPBP y esa clase la puedes compilar como un ensamblado DLL y usarlo desde un proyecto de C#.

Espero que te sea de utilidad.

Nos vemos.
Guillermo

 


Espacios de nombres usados en el código de este artículo:

System.IO
System.Text
 


Código de ejemplo (comprimido):

Fichero con el código de ejemplo: Extraer_PBP.zip - 24.90 KB

Contiene el proyecto de Visual Basic 2008.

(MD5 checksum: 7EAAE0FC413D929867EDEE262A2EFAB4)


 


La fecha/hora en el servidor es: 22/11/2024 23:34:22

La fecha actual GMT (UTC) es: 

©Guillermo 'guille' Som, 1996-2024