NWN Data Files 엘더스크롤 NWN DA

원출처 : www.torlack.com/index.html?topics=nwndata_itp 

What is an ITP File?

(Note: ITP files are more correctly known as the GFF and BioWare's documentation can be found here.) nwn.bioware.com/developers/gff.html 

An ITP file can be thought of as a hierarchical collection variables.

That cleared it up, didn't it.

In an ITP file, you have a series of what can be considered variable assignments such as 'PlayerName = "Francis"'. There can practically be an infinite number of variable assignments in one file. Also, ITP files allow you to nest variable assignments much the way a programmer might nest structures.

ITP files are very common in NWN. The format is used for a wide collection of files such as; ITP, BIC, DLG, GIT, GFF, FAC, UTI, UTC, UTT, UTS, UTE, UTD, UTP, UTM, and UTW. All of these different files share the same common file format. So in reality calling the file format ITP is inaccurate, but it is the name we all know best.

Data Stored in an ITP File
ITP files contain seven sections of data. The sections are as follows:

File Header - Gives us enough information to begin decoding the remaining sections.
Entry Table - Provides us with our collections of variables and their nesting.
Element Table - Provides the information about all the variables contained within an entry.
Variable Names Table - List of all the different variables names.
Variable Data Section - Depending on the element in question, this data block can contain a assortment of data.
Multiple Element Map (MultiMap) - Used to list which elements are contained within an entry when that entry contains more than one element.
List Section - Provides a method for an element to have one or more entries as children.
The File Header
Offset Type Description

OffsetTypeDescription
0x0000char [4]4 byte signature.
0x0004char [4]4 byte version.
0x0008UINT32Offset from start of the file to the first entry
0x000CUINT32Number of entries in the file
0x0010UINT32Offset from start of the file to the first element
0x0014UINT32Number of elements in the file
0x0018UINT32Offset from start of the file to the first variable name
0x001CUINT32Number of names in the variable name block
0x0020UINT32Offset from start of the file to the first variable data
0x0024UINT32Number of bytes in the variable data block
0x0028UINT32Offset from start of the file to the first multimap
0x002CUINT32Number of bytes in the multimap table
0x0030UINT32Offset from start of the file to the first list
0x0034UINT32Number of bytes in the list table
0x0038Total length of the structure

All offsets in the file header are from the start of the file. The file header is always at offset zero. It seems that the offset to the first entry is always 0x0038. But please note that for entries and elements, we are given the number of entries or elements. For the four remaining data sections, the total section length is given in bytes.

The Entity Table
The entity table is an array of structures of the following form:

OffsetTypeDescription
0x0000UINT32Entity code (Use unknown)
0x0004UINT32Element index or MultiMap offset
0x0008UINT32Number of elements in this entry
0x000CTotal length of the structure

The first entry in the entry table is the root of whole hierarchy. From that one entry, all other entries and elements can be accessed in a logical form.

Entries only serve one function, to contain one or more elements. Depending on the number of elements contained in an entry, accessing the elements can either be easy or require indirect referencing. When an entry contains only one element, and thus the value at offset 0x0008 is one, then the value at offset 0x0004 is the index in the element table of the only child. When an entry contains more than one element, then the value at offset 0x0004 contains a byte offset into the multimap table. This offset points to an array of element numbers stored as UINT32 values.

Here is an example of how you might iterate through the list of elements for an entity. This code assumes that the whole file is resident in memory in a contiguous buffer.

char *pFileData = AddressOfTheITPDataInMemory;
Header *pHeader = (Header *) pFileData;
Entry *pEntryTable = (Entry *) &pFileData [pHeader ->EntryOffset];
Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];

Entry *pEntry = &pEntryTable [0]; // For this example, use root entry

if (pEntry ->Count == 1) // We have one element
{
Element *pElement = &pElementTable [pEntry ->Offset];
// Do something with the element
}
else //more than one
{
UINT32 *pMMap = (UINT32 *) &pFileData [pHeader ->MultiMapOffset + pEntry ->Offset];
for (int i = 0; i < pEntry ->Count; i++)
{
Element *pElement = &pElementTable [pMMap [i]];
// Do something with the element
}
}

There is one trick you can use to reduce duplicated code. I use this trick in my software.

char *pFileData = AddressOfTheITPDataInMemory;
Header *pHeader = (Header *) pFileData;
Entry *pEntryTable = (Entry *) &pFileData [pHeader ->EntryOffset];
Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];

Entry *pEntry = &pEntryTable [0]; // For this example, use root entry

UINT32 *pMMap;
if (pEntry ->Count == 1)
pMMap = &pEntry ->Offset;
else
pMMap = (UINT32 *) &pFileData [pHeader ->MultiMapOffset + pEntry ->Offset];

for (int i = 0; i < pEntry ->Count; i++)
{
Element *pElement = &pElementTable [pMMap [i]];
// Do something with the element
}

The Element Table
The element table is an array of structures of the following form:

OffsetTypeDescription
0x0000UINT32Variable type (0-15)
0x0004UINT32Variable name index
0x0008VARIESData for the variable
0x000CTotal length of the structure

As you can see, the element structure is very basic. From the structure we know the name of the variable, the type of the variable, and the assigned value of the variable. The only difficult part is extracting the value from the structure.

There are 16 know variable types supported by the ITP format. They are as follows:

ValueTypeAccessData
0UINT8DirectUnsigned byte
1INT8DirectSigned byte
2UINT16DirectUnsigned word
3INT16DirectSigned word 
4UINT32DirectUnsigned longword 
5INT32DirectSigned longword 
6UINT64IndirectUnsigned quadword 
7INT64IndirectSigned quadword
8FLOATIndirectFour byte floating point value
9DOUBLEIndirectEight byte floating point value
10STRINGIndirectCounted string
11RESREFIndirectCounted resource name
12STRREFComplexMultilingual capable string
13DATREFComplexCounted binary data 
14CAPREFComplexList of child elements
15LISTComplexList of child entries

For the simple data types, UINT8, INT8, UINT16, INT16, UINT32, INT32 and FLOAT, the value is just stored directly into offset 0x0008. Since NWN is for Intel systems, all the data values are stored in little endian format. Thus, for UINT8 and INT8, the value is a single byte stored literally at offset 0x0008 in the element structure. A UINT16 or INT16 takes up the first two bytes. The remaining three basic data types take up all four bytes. Access to the different values of the element can be done with this structure.

struct Element
{
UINT32 Type;
UINT32 NameIndex;
union
{
UINT8 ui8;
INT8 si8;
UINT16 ui16;
INT16 si16;
UINT32 ui32;
INT32 si32;
FLOAT flt;
UINT32 Offset;
};
};

Note: The "Offset" member can be used to access the indirect and complex data stored in the variable data section..

In the case of UINT64, INT64 and DOUBLE, the value stored at offset 0x0008 is a byte offset into the variable data region. Starting at that address would be stored the value in question.

char *pFileData = AddressOfTheITPDataInMemory;
Header *pHeader = (Header *) pFileData;
Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];

Element *pElement = &pElementTable [0]; // For this example, use first element

if (pElement ->Type == 6)
{
UINT64 ul64 = *((UINT64 *) &pFileData [
pHeader ->VarDataOffset + pElement ->Offset]);
}
else if (pElement ->Type == 7)
{
INT64 l64 = *((INT64 *) &pFileData [
pHeader ->VarDataOffset + pElement ->Offset]);
}
else if (pElement ->Type == 9)
{
double d = *((double *) &pFileData [
pHeader ->VarDataOffset + pElement ->Offset]);
}

The STRING and RESREF types are two more indirect types but require a bit more work to access. For both the STRING and RESREF the actual data is stored in the variable data section starting at the given offset. They both being with the length of string. Following the length, are the actual bytes of the string. The string is not NULL terminated. However, STRING and RESREF do differ. In the case of STRING, the string length is stored as a UINT32 value. In the case of RESREF, the string length is stored as a UINT8 value.

char *pFileData = AddressOfTheITPDataInMemory;
Header *pHeader = (Header *) pFileData;
Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];

Element *pElement = &pElementTable [0]; // For this example, use first element

if (pElement ->Type == 10) //STRING
{
UINT32 Length = *((UINT32 *) &pFileData [
pHeader ->VarDataOffset + pElement ->Offset]);
char *String = (char *) &pFileData [
pHeader ->VarDataOffset + pElement ->Offset +
sizeof (UINT32)]
}
else if (pElement ->Type == 11) //RESREF
{
UINT8 Length = *((UINT8 *) &pFileData [
pHeader ->VarDataOffset + pElement ->Offset]);
char *String = (char *) &pFileData [
pHeader ->VarDataOffset + pElement ->Offset +
sizeof (UINT8)]
}

The STRREF has two uses. First, it references a string in the dialog.tlk file. Second, it provides localized (human language specific) version of the string. The offset in the element points to a variable length structure that begins with the following 12 bytes of data.

OffsetTypeDescription
0x0000UINT32Number of bytes included in the STRREF not including this count.
0x0004INT32ID of the string in the in the dialog.tlk file.  If none, then -1.
0x0008UINT32Number of language specific strings.  This value is usually zero.
0x000CTotal length of the structure

If the number of language specific strings is zero, then the length of the total STRREF is only 12 bytes. The text of the string can be retrieved from dialog.tlk. The value stored as the size of the STRREF will be 8 since the 4 bytes in the count aren't included. However, if the number of language specific strings is not zero, then there will be the given number of the following structure following the initial STRREF structure.

OffsetTypeDescription
0x0000UINT32Language of the string (values unknown)
0x0004UINT32Number of bytes in the following string
0x0008CHAR []Variable length string
VARIESTotal length of the structure will always be 8 plus the length of the string

The DATREF associates a block of arbitrary binary data with the element in question. At the offset in the variable data section given in the element is a UINT32 that has the number of bytes in the DATREF. Following the UINT32 is the binary data.

The CAPREF element type allows for an element to contain an array of other elements by referencing a single entry. The offset in the element structure specifies the entry number. All the elements that entry references can be considered as the children of the element.

The LIST element type allows for an element to contain an array of other entries. For a LIST type, the offset in the element structure specifies an offset into the list table. This is a byte offset even though the table itself is just a series of UINT32 values. At the first UINT32 pointed to by the offset will be the number of entries referenced by the element. Following this count will be the actual entry numbers.

char *pFileData = AddressOfTheITPDataInMemory;
Header *pHeader = (Header *) pFileData;
Element *pElementTable = (Element *) &pFileData [pHeader ->ElementOffset];

Element *pElement = &pElementTable [0]; // For this example, use first element

if (pElement ->Type == 15) //LIST
{
UINT32 *List = *((UINT32 *) &pFileData [
pHeader ->ListDataOffset + pElement ->Offset]);
UINT32 EntryCount = List [0];
for (int i = 0; i < (int) EntryCount; i++)
{
UINT32 EntryIndex = List [i + 1];
// do something with Entry
}
}

The Variable Name Table
The variable name table is a series of variable names stored in 16 byte long character strings. Care should be taken when processing the variable name strings. If the string is less than 16 characters in length, it will be NULL terminated. However, if the variable name is 16 characters long, then it will not. The easiest thing to do is copy the string into a 17 byte character string and then set the 17th character to NULL. Thus, if the string is 16 characters long, the 17th character will force a NULL termination.

The Remaining Tables
All other data tables are purely subservient to the entry and element tables. See the section on the elements to see how this information is interpreted.

Credits

Even though I was able to decode much of the ITP format soon after the game was released, I want to thank Logxen, Seg Falt and Chanteur for the information they published about the file format. Their information filled in some very important missing elements such as CAPREF, DATREF and elements of STRREF.



덧글

댓글 입력 영역