Autor
|
Tema: Ayuda para parsear este formato... (Leído 5,929 veces)
|
Eleкtro
Ex-Staff
Desconectado
Mensajes: 9.878
|
Buenas! Primero me gustaría aclarar que el propósito de esto es ético, y cualquier persona dispuesta a ayudarme a aclararme las ideas con esta duda que tengo, podría verse recompensada en un futuro próximo, ya que un grupo muy importante (y veterano) de Rom-Hacking está llevando a cabo la traducción al Español del videojuego Persona 3 para PS2, y yo he decidido prestarles ayuda en una pequeña cosa que necesitan, pero me encuentro atascado... Este es el archivo con el que estoy trabajando: El archivo se llama datBootsHelp.bmd y el formato del archivo, BMD, es un formato privativo el cual contiene 2 tablas, necesito parsear una de esas tablas para traducir de forma programática los textos que contiene, y por textos me refiero a estas descripciones en Inglés: En la siguiente imagen os muestro las especificaciones conocidas del formato BMD, y los valores que he obtenido analizando el archivo de ejemplo datBootsHelp.bmd el cual he compartido al principio del post. Las filas marcadas con un signo verde son las que he conseguido localizar y parsear de forma programática sin problemas, las filas con un aspa roja son las que todavía no, y la fila con un signo de interrogación es el offset con el que tengo problemas y por ende me impide localizar los valores del aspa roja... El problema que tengo es que no consigo determinar en que offset se delimita el final de la primera tabla, con el inicio de la segunda tabla, ni tampoco entiendo cual es la longitud exacta (contando los bytes ceros/nulos, se entiende) de los campos/textos de la segunda tabla, es decir, no consigo entender donde empieza el identificador de texto, cual es su longitud/capacidad, y donde empieza la descripción de texto y cual es su longitud/capacidad. Si analizo y comparo cada campo por individual para intentar averiguar la longitud, me da diferentes longitudes así que no me cuadra nada... pero eso solo significa que lo estoy analizando mal, por que deben tener una longitud máxima el identificador la descripción, vaya xD. Supuestamente y a mi entender, la primera tabla termina en el offset 814 (0x32C) como se menciona en la imagen de arriba; en esta imagen de aquí abajo hago una selección empezando por ese offset hasta encontrar el inicio del primer identificador de texto ...y por medio hay 2 bytes (A4 1D) que no se lo que son:
Por último, comparto el código fuente que he desarrollado en el lenguaje VB.NET para leer los datos de la cabecera del formato y la primera tabla: ''' ---------------------------------------------------------------------------------------------------- ''' <summary> ''' Represents the BMD File Format. ''' </summary> ''' ---------------------------------------------------------------------------------------------------- ''' <remarks> ''' <see href="http://datacrystal.romhacking.net/wiki/Persona_3_and_4/BMD_%28File_Format%29"/> ''' </remarks> ''' ---------------------------------------------------------------------------------------------------- Public NotInheritable Class BMDFormat #Region " Private Fields " ''' <summary> ''' Specifies the offset where the header begins. ''' </summary> Private ReadOnly HeaderOffset As Integer = &H0 ''' <summary> ''' Specifies the offset where the non-text table begins. This is the first table in the BMD file format. ''' </summary> Private ReadOnly TableNonTextOffset As Integer = &H24 ''' <summary> ''' Specifies the offset where the text table begins. This is the second table in the BMD file format. ''' </summary> Private TableTextOffset As Integer ' The value is determined later, at the end of the TableNonTextOffset table. ''' <summary> ''' Contains the offset location and length of the BMD header offsets. ''' </summary> Private ReadOnly HeaderOffsets As New Dictionary(Of String, KeyValuePair (Of Integer, Integer))(StringComparer. Ordinal) From { {"ChunkID", New KeyValuePair(Of Integer, Integer)(&H0, Nothing)}, {"Size", New KeyValuePair(Of Integer, Integer)(&H4, &H5)}, {"MagicIdentifier", New KeyValuePair(Of Integer, Integer)(&H8, &HB)}, {"EndOfTextTable", New KeyValuePair(Of Integer, Integer)(&H10, &H11)}, {"OffsetsAmount", New KeyValuePair(Of Integer, Integer)(&H14, &H15)}, {"EntriesAmount", New KeyValuePair(Of Integer, Integer)(&H18, &H19)} } ' Friendly-Name, {Start-Offset, End-Offset} #End Region #Region " Properties " ''' <summary> ''' Gets the raw byte-data of the BMD file. ''' </summary> Public ReadOnly Property RawData As Byte() Get Return Me.rawDataB End Get End Property Private ReadOnly rawDataB As Byte() ''' <summary> ''' Gets the Chunk ID of the file format. ''' </summary> Public ReadOnly Property ChunkID As Byte Get Return Me.chunkIDB End Get End Property Private chunkIDB As Byte ''' <summary> ''' Gets the size of the file. ''' </summary> Public ReadOnly Property Size As Short Get Return Me.sizeB End Get End Property Private sizeB As Short ''' <summary> ''' Gets the Magic Identifier of the file format. ''' </summary> Public ReadOnly Property MagicIdentifier As Byte() Get Return Me.magicIdentifierB End Get End Property Private magicIdentifierB As Byte() ''' <summary> ''' Gets the end of text table of the file format. ''' </summary> Public ReadOnly Property EndOfTextTable As Short Get Return Me.endOfTextTableB End Get End Property Private endOfTextTableB As Short ''' <summary> ''' Gets the amount of offsets in offset table of the file format. ''' </summary> Public ReadOnly Property OffsetsAmount As Short Get Return Me.offsetsAmountB End Get End Property Private offsetsAmountB As Short ''' <summary> ''' Gets the amount of entries of the file format. ''' </summary> Public ReadOnly Property EntriesAmount As Short Get Return Me.entriesAmountB End Get End Property Private entriesAmountB As Short #End Region #Region " Constructors " Private Sub New() End Sub Public Sub New(ByVal filepath As String) Me. New(File. ReadAllBytes(filepath )) End Sub Public Sub New(ByVal file As FileInfo ) End Sub Public Sub New(ByVal raw As Byte()) Me.rawDataB = raw Me.ReadHeader() End Sub #End Region #Region " Private Methods " ''' <summary> ''' Reads the BMD header. ''' </summary> Private Sub ReadHeader() Dim chunkID As KeyValuePair(Of Integer, Integer) = Me.HeaderOffsets("ChunkID") Dim size As KeyValuePair(Of Integer, Integer) = Me.HeaderOffsets("Size") Dim magicIdentifier As KeyValuePair(Of Integer, Integer) = Me.HeaderOffsets("MagicIdentifier") Dim endOfTextTable As KeyValuePair(Of Integer, Integer) = Me.HeaderOffsets("EndOfTextTable") Dim offsetsAmount As KeyValuePair(Of Integer, Integer) = Me.HeaderOffsets("OffsetsAmount") Dim entriesAmount As KeyValuePair(Of Integer, Integer) = Me.HeaderOffsets("EntriesAmount") Me.magicIdentifierB = Me.GetBytes(Me.HeaderOffset, magicIdentifier.Key, magicIdentifier.Value) Me.chunkIDB = Me.GetByte(Me.HeaderOffset, chunkID.Key) Me.sizeB = BitConverter.ToInt16(Me.GetBytes(Me.HeaderOffset, size.Key, size.Value), 0) Me.endOfTextTableB = BitConverter.ToInt16(Me.GetBytes(Me.HeaderOffset, endOfTextTable.Key, endOfTextTable.Value), 0) Me.offsetsAmountB = BitConverter.ToInt16(Me.GetBytes(Me.HeaderOffset, offsetsAmount.Key, offsetsAmount.Value), 0) Me.entriesAmountB = BitConverter.ToInt16(Me.GetBytes(Me.HeaderOffset, entriesAmount.Key, entriesAmount.Value), 0) Me.TableTextOffset = Me.TableNonTextOffset + (8 * (entriesAmountB - 1)) + 2 Me.ReadNonTextTable() Me.ReadTextTable() End Sub ''' <summary> ''' Reads the non-text table of the BMD file format. ''' <para></para> ''' This table seems not useful. ''' </summary> Private Sub ReadNonTextTable() For x As Integer = 0 To (entriesAmountB - 1) Dim offset As Integer = Me.TableNonTextOffset + (8 * x) Dim data As Byte() = Me.GetBytes(offset, 0, 1) Dim value As Short = BitConverter.ToInt16(data, 0) Debug. WriteLine(String. Format("Entry.Index.: {0}", (x + 1))) Debug. WriteLine(String. Format("Start.Offset: DEC={0,-4} HEX=0x{1}", offset, offset. ToString("X"))) Debug. WriteLine(String. Format("Raw.Bytes...: {0}", String. Join(" ", From b As Byte In data Select b. ToString("X")))) Debug. WriteLine(String. Format("Int16.Value.: {0}", value )) Debug. WriteLine(String. Empty) #End If Next End Sub ''' <summary> ''' Reads the text table of the BMD file format. ''' <para></para> ''' This table contains the items identifiers and their descriptions for further translation. ''' </summary> Private Sub ReadTextTable() For x As Integer = 0 To ... ' ...? Next End Sub Private Function GetByte(ByVal start As Integer, ByVal offset As Integer) As Byte Return Buffer.GetByte(array:=Me.RawData, index:=start + offset) End Function Private Function GetBytes(ByVal start As Integer, ByVal from As Integer, ByVal [to] As Integer) As Byte() Return Me.rawDataB.Skip(start + from).Take(([to] - from) + 1).ToArray() End Function #End Region End Class
Saludos!
|
|
« Última modificación: 31 Diciembre 2016, 16:38 pm por Eleкtro »
|
En línea
|
|
|
|
ivancea96
Desconectado
Mensajes: 3.412
ASMático
|
Obtengo estos datos: boots_2000 boots_2001 boots_2002 boots_2003 boots_2004 boots_2005 boots_2006 boots_2007 boots_2008 boots_2009 boots_200A boots_200B boots_200C boots_200D boots_200E boots_200F boots_2010 boots_2011 boots_2012 boots_2013 boots_2014 boots_2015 boots_2016 boots_2017 boots_2018 boots_2019 boots_201A boots_201B boots_201C boots_201D boots_201E boots_201F boots_2020 boots_2021 boots_2022 boots_2023 boots_2024 boots_2025 boots_2026 boots_2027 boots_2028 boots_2029 boots_202A boots_202B boots_202C boots_202D boots_202E boots_202F boots_2030 boots_2031 boots_2032 boots_2033 boots_2034 boots_2035 boots_2036 boots_2037 boots_2038 boots_2039 boots_203A boots_203B boots_203C boots_203D boots_203E boots_203F boots_2040 boots_2041 boots_2042 boots_2043 boots_2044 boots_2045 boots_2046 boots_2047 boots_2048 boots_2049 boots_204A boots_204B boots_204C boots_204D boots_204E boots_204F boots_2050 boots_2051 boots_2052 boots_2053 boots_2054 boots_2055 boots_2056 boots_2057 boots_2058 boots_2059 boots_205A boots_205B boots_205C boots_205D boots_205E boots_205F boots_2060 boots_2061 Con este código en C++: #include <fstream> #include <iostream> #include <vector> using namespace std; struct BMDHeader{ uint8_t chunkId; uint8_t __pad0[3]; uint16_t fileSize; uint8_t __pad1[1]; uint32_t magicIdentifier ; uint32_t _null_ : 32; uint16_t textTableEnd; uint8_t __pad2[2]; uint16_t offsetAmount; uint8_t __pad3[2]; uint8_t entryAmount; uint8_t __pad4[3]; uint32_t _20000_; }; int main(){ /// READ FILE fstream inFile("datBootsHelp.bmd", ios::binary | ios::in); if(!inFile){ cout << "File error" << endl; return 1; } inFile.seekg(0, ios::end); string file(inFile.tellg(), '\0'); inFile.seekg(0); inFile.read(const_cast<char*>(file.data()), file.size()); /// FILE READED /// GETTING HEADER BMDHeader header = *(BMDHeader*)file.data(); if(header._20000_ != 0x20000 || header.fileSize != file.size()){ cout << "Header error" << endl; return 2; } /// HEADER OK cout << (int)header.entryAmount << " entries" << '\n' << endl; /// READING VALUES vector<string> entries(header.entryAmount); for(int i=0; i<header.entryAmount; i++){ uint16_t offset = *(uint16_t*)&file[0x24 + 8 * i]; entries[i] = string(&file[0x20 + offset]); } for(string& str : entries){ cout << str << '\n'; } cout << '\n' << "ENDED" << endl; }
Eso sí, ignoré gran parte de los campos del header. No sé si son necesarias. Y ya podías decir al menos de qué trataba el formato eh? Que tuve que descubrirlo xD http://datacrystal.romhacking.net/wiki/Persona_3_and_4/BMD_(File_Format)EDITO: Un poco hardcodeado para obtener los datos que faltan (a parte del ID): #include <fstream> #include <iostream> #include <map> using namespace std; struct BMDHeader{ uint8_t chunkId; uint8_t __pad0[3]; uint16_t fileSize; uint8_t __pad1[1]; uint32_t magicIdentifier ; uint32_t _null_ : 32; uint16_t textTableEnd; uint8_t __pad2[2]; uint16_t offsetAmount; uint8_t __pad3[2]; uint8_t entryAmount; uint8_t __pad4[3]; uint32_t _20000_; }; int main(){ /// READ FILE fstream inFile("datBootsHelp.bmd", ios::binary | ios::in); if(!inFile){ cout << "File error" << endl; return 1; } inFile.seekg(0, ios::end); string file(inFile.tellg(), '\0'); inFile.seekg(0); inFile.read(const_cast<char*>(file.data()), file.size()); /// FILE READED /// GETTING HEADER BMDHeader header = *(BMDHeader*)file.data(); if(header._20000_ != 0x20000 || header.fileSize != file.size()){ cout << "Header error" << endl; return 2; } /// HEADER OK cout << (int)header.entryAmount << " entries" << '\n' << endl; /// READING VALUES map<string, string> entries; for(int i=0; i<header.entryAmount; i++){ uint16_t offset = *(uint16_t*)&file[0x24 + 8 * i]; string id = string(&file[0x20 + offset]); entries[id] = string(&file[0x20 + offset + id.size() + 0x20]); } for(auto& p : entries){ cout << p.first << ":\n"; cout << p.second << "\n\n"; } cout << '\n' << "ENDED" << endl; }
Datos: boots_2000: Reserve
boots_2001: Solidly made work boots.
boots_2002: An ordinary pair of loafers.
boots_2003: Long, black boots.
boots_2004: Boots with a horizontal line over the toes.
boots_2005: Classic-model sneakers.
boots_2006: Shoes for playing indoor soccer.
boots_2007: Low-heeled boots.
boots_2008: Sandals that massage your pressure points.
boots_2009: Strong enough to take any punishment.
boots_200A: Boots with thick soles.
boots_200B: Sandals with many bumps.
boots_200C: Boots made out of leather.
boots_200D: Shoes with a single row of wheels.
boots_200E: Geta made with the latest technology.
boots_200F: Boots from military surplus.
boots_2010: Greaves colored a dark red.
boots_2011: Sandals that boost the wearer's magic.
boots_2012: Shoes made with modern technology.
boots_2013: Sandals worn by ninjas.
boots_2014: Shoes with needles on the soles.
boots_2015: Boots with jet engines attached.
boots_2016: Exceptionally durable greaves.
boots_2017: Very light shoes.
boots_2018: Sandals with dazzling silver ornaments.
boots_2019: 0x2019
boots_201A: 0x201A
boots_201B: Shoes with pentacle symbols.
boots_201C: Hitmen's favorite shoes.
boots_201D: Unbelievably light pair of sandals.
boots_201E: A war god's spirit dwells in these boots.
boots_201F: Boots that have been blessed.
boots_2020: Women's boots that lend beauty.
boots_2021: A famous ninja's sandals.
boots_2022: 0x2022
boots_2023: 0x2022
boots_2024: Men's boots embroidered with dragons.
boots_2025: Can supposedly be remote-controlled...
boots_2026: 0x2026
boots_2027: A manly pair of geta, for men.
boots_2028: Shoes with vicious- looking spikes.
boots_2029: 0x2029
boots_202A: Sandals with a lion drawn on them.
boots_202B: Men's greaves, worn by a veteran warrior.
boots_202C: 0x202C
boots_202D: 0x202D
boots_202E: Sandals with a fresh grass scent.
boots_202F: 0x202F
boots_2030: 0x2030
boots_2031: 0x2031
boots_2032: Savior's boots worn only by men.
boots_2033: 0x2033
boots_2034: Leopard-print leggings for women.
boots_2035: 0x2035
boots_2036: Lightweight pumps for women.
boots_2037: 0x2037
boots_2038: Heels that make women supersexy.
boots_2039: 0x2039
boots_203A: 0x203A
boots_203B: 0x203B
boots_203C: Women's boots that last forever.
boots_203D: The paw pads are very soft to the touch.
boots_203E: 0x203E
boots_203F: 0x203F
boots_2040: 0x2040
boots_2041: 0x2041
boots_2042: 0x2042
boots_2043: 0x2043
boots_2044: 0x2044
boots_2045: 0x2045
boots_2046: 0x2046
boots_2047: Ordinary leg parts for Aigis.
boots_2048: Leg parts for Aigis made with strong fibers.
boots_2049: Processed iron leg parts for Aigis.
boots_204A: Molecularly cohesive leg parts for Aigis.
boots_204B: Ceramic leg parts for Aigis.
boots_204C: Cobalt leg parts for Aigis.
boots_204D: Lightweight leg parts for Aigis.
boots_204E: Alloyed leg parts for Aigis.
boots_204F: Consolidated leg parts for Aigis.
boots_2050: Leg parts for Aigis from the Beast God.
boots_2051: 2051
boots_2052: 2052
boots_2053: Shining leg parts for Metis.
boots_2054: Leather-covered leg parts for Metis.
boots_2055: Resin leg parts for Metis.
boots_2056: Light ceramic leg parts for Metis.
boots_2057: Light alloy leg parts for Metis.
boots_2058: Titanium leg parts for Metis.
boots_2059: Zirconium leg parts for Metis.
boots_205A: Imitation dancing shoes for Metis.
boots_205B: Magical leg parts for Metis.
boots_205C: Crimson leg parts for Metis.
boots_205D: Angelic leg parts for Aigis.
boots_205E: Mysterious glass leg parts for Metis.
boots_205F: The ultimate prototype leg parts for Aigis.
boots_2060: Supreme pitch-black leg parts for Metis.
boots_2061: ü·ü╩ü┘ Tengo que ver cómo obtener los datos, ya que no parece que hablen de ello en lo del formato. EDITO: Mejorado. Ahora capta bien el mensaje (salvo para el último, que no parece tener el mismo formato (o el formato está mal interpretado)) Y con respecto a lo que dices de cuándo acaba la primera tabla y cuándo empieza la segunda, yo me limité a lo que dice esa web y utilizar directamente el offset "Text offset relative from offset 20". Sumarlo, para saber la posición de la celda de texto, y listo. Iterar por cada elemento de la primera tabla, y se acabó.
|
|
« Última modificación: 11 Diciembre 2016, 18:52 pm por ivancea96 »
|
En línea
|
|
|
|
Eleкtro
Ex-Staff
Desconectado
Mensajes: 9.878
|
@ivancea96 Joder, no me esperaba una ayuda tan gratificante, ¡muchísimas gracias!. Más tarde me pondré a analizar el código e intentar reproducirlo en VB.NET.
PD: No quise decir de que trataba el formato BMD por cuestiones éticas, para no dar motivo a que se cierre el post, de todas formas en el código que compartí, arriba en la cabecera habia una url a esa wiki... xD.
EDITO: Veo que la cantidad de identificadores de items (boots_*) que obtuviste son 98, y el valor "98" se puede obtener en la cabecera del formato, y además el 98 también hace referencia a la cantidad de valores que hay en la primera tabla, así que deduzco que esos 98 valores de la primera tabla deberán especificar la longitud de cada bloque de la segunda tabla, genial! Saludos y gracias de nuevo por tu tiempo!
|
|
« Última modificación: 13 Diciembre 2016, 11:10 am por Eleкtro »
|
En línea
|
|
|
|
Eleкtro
Ex-Staff
Desconectado
Mensajes: 9.878
|
Hola. Dejé a un lado este proyecto por un tiempo y hoy he vuelto a ponerme. @ ivancea96, estoy adaptando tu código, pero tengo una duda... ¿Cómo determinas la longitud del campo "id" (9 caracteres legibles en este caso, 42 de espacio en total creo) y del texto/descripción del item?. En esta iteración no veo que determines la longitud de "id" ni tampoco de "entries[0]", ¿como es posible entonces?, no lo entiendo. for(int i=0; i<header.entryAmount; i++){ uint16_t offset = *(uint16_t*)&file[0x24 + 8 * i]; string id = string(&file[0x20 + offset]); entries[id] = string(&file[0x20 + offset + id.size() + 0x20]); }
En serio, ¿como determinas el tamaño que tiene "id" y cada elemento del array "entries"?. EDITO: Estoy leyendo una referencia online de C++, no se si esto tiene algo que ver pero creo que cuando usas string file(inFile.tellg(), '\0'); devuelve la posición del caracter evaluado "\0" (¿está \escapado?, no me queda muy claro si eso es un "0", no se nada de c/c++).
Esta sería mi adaptación de tu iteración, INACABADA... por que desconozco la longitud de esos campos. For x As Integer = 0 To (entryAmountB - 1) Dim offset As Integer = Me.TableNonTextOffset + (8 * x) Dim value As Short = BitConverter.ToInt16(Me.GetBytes(offset, 0, 1), 0) Dim idOffset As Short = (&H20 + value) Dim id As String = Encoding.UTF8.GetString(Me.GetBytes(idOffset, 0, LONGITUD_DE_ID)) Dim textOffset As Short = (idOffset + id.Length() + &H20) Dim text As String = Encoding.UTF8.GetString(Me.GetBytes(textOffset, 0, LONGITUD_DE_TEXTO)) Debug. WriteLine(String. Format("Entry.Id...: {0}", id )) ' boots_nnnn Debug. WriteLine(String. Format("Entry.Text.: {0}", text )) ' el texto/descripción del item Next
Cada texto de "boots_nnnn" termina con una secuencia de bytes parecida a estas: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 FF FF 44 03 00 00 0F 00 00 00 F2 08 FF FF F1 3F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 FF FF 78 03 00 00 20 00 00 00 F2 08 FF FF F1 3F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 FF FF C4 05 00 00 2D 00 00 00 F2 08 FF FF F1 3F Despues de esa secuencia comienza el texto/descripción del item, y ese texto siempre parece terminar con la secuencia de bytes 2E 0A. Sabiendo eso, creo que puedo determinar facilmente la longitud de cada campo, pero me gustaría comprender como lo haces en tu código, por que pareces conocer la longitud de forma más simple y eficiente. Saludos!
|
|
« Última modificación: 31 Diciembre 2016, 16:42 pm por Eleкtro »
|
En línea
|
|
|
|
ivancea96
Desconectado
Mensajes: 3.412
ASMático
|
el constructor de la clase string en C++, si no le pasas un tamaño, coge hasta el próximo caracter nulo ('\0'). Si no recuerdo mal, ese campo es de tamaño variable, y el resto de la memoria, la no utilizada, está rellena con caracteres nulos. Así que eso, dura ahsta el próximo caracter nulo.
|
|
|
En línea
|
|
|
|
|
Mensajes similares |
|
Asunto |
Iniciado por |
Respuestas |
Vistas |
Último mensaje |
|
|
Ayuda para ver un dvd que esta en formato .iso!!!
Multimedia
|
brank87
|
2
|
2,230
|
14 Octubre 2005, 22:10 pm
por brank87
|
|
|
necesito ayuda para cambiar el formato!!
Multimedia
|
gaye
|
2
|
2,283
|
7 Noviembre 2005, 18:20 pm
por ™Carlos.®
|
|
|
ayuda para cambiar formato de mpg a un avi
Multimedia
|
morbid
|
3
|
2,345
|
27 Diciembre 2005, 18:27 pm
por Songoku
|
|
|
ayuda para convertir de .avi(Xdiv) a formato DVD
Multimedia
|
Commander
|
3
|
3,608
|
10 Febrero 2008, 06:43 am
por sempus
|
|
|
Programa para quemar peliculas bajadas en formato divx y avi, a formato dvd
Multimedia
|
xoux
|
3
|
5,677
|
28 Abril 2008, 08:37 am
por Aberroncho
|
|