Texture files are all files in the ARENA2 game directory that match TEXTURE.??? where ??? is a 3-digit ASCII encoded number, ranging from "000" through "511", with some numbers missing for a total of 472 files.
General File LayoutEdit
The overall texture format is slightly more complicated than the usual image format. With only four exceptions, a texture file begins with a 26 byte TextureFileHeader structure, followed by a contiguous list of RecordHeader structures, followed by the actual data which composes each record. The record data itself is not in a single, uniform format, but instead comes in different formats, detailed below.
TextureFileHeader |
---|
?RecordHeaderList |
?RecordData |
TextureFileHeaderEdit
Offset | Type | Name | Description |
---|---|---|---|
0-1 | Int16 | RecordCount | The count of RecordHeader elements which follow this header. |
2-25 | 7-bit ASCII string | Name | A name or description of the texture file. This value is null-terminated. |
RecordHeaderEdit
0-1 | UInt16 | Unknown1 | Unknown purpose. |
---|---|---|---|
2-5 | Int32 | RecordPosition | The offset to the record's data position within the file, relative to the start of the file. |
6-7 | UInt16 | Unknown2 | Unknown purpose. |
8-11 | UInt32 | Unknown3 | Unknown purpose. |
12-19 | Int64 | NullValue | Always 0x0000000000000000 |
Record DataEdit
Immediately following the RecordHeaderList is the data which composes the individual records of the file. Sections of this file require different decoding mechanisms (described below) in order to read the individual records.
TextureRecordEdit
Starting at each RecordHeader.RecordPosition is a TextureRecord structure, which describes the various properties and attributes of the record, such as the number of frames or compression used to store the frame data.
A TextureRecord structure is very similar to an ImgHeader structure, but most notably the offset and size fields are transposed. To decode the data referenced by the DataOffset field, one must be aware there are different subformats. The appropriate decoder is selected by consulting the Compression, IsNormal, and FrameCount fields. The reader is reminded that while most records encountered will define an image with but a single frame, there are several textures with multiframe animation records. The swirling animation which frames enchanted items in the inventory screen is one such example of a multiframe animation texture record. Most of the light-sources encountered in the game (torches, candles, fires, etc.) are more such examples.
After navigating to the address specified by the DataOffset field, one may begin reading and decoding the record data.
Offset | Type | Name | Description |
---|---|---|---|
0-1 | Int16 | OffsetX | The X-axis display offset for the record's image. |
2-3 | Int16 | OffsetY | The Y-axis display offset for the record's image. |
4-5 | Int16 | Width | The width of the record's image. |
6-7 | Int16 | Height | The height of the record's image. |
8-9 | Compression (UInt16) | Compression | The Compression enumeration is detailed here. Any unknown values should be interpretted as Uncompressed. |
10-13 | UInt32 | RecordSize | The total size of the record's image data, including this 28 byte structure. |
14-17 | UInt32 | DataOffset | The position of the record's image data from the start of this structure. |
18-19 | Bool16 | IsNormal | This flag is required to decode the record's image data (detailed below). |
20-21 | UInt16 | FrameCount | The count of animation frames for this record's image. |
22-23 | UInt16 | Unknown1 | Unknown purpose. |
24-25 | Int16 | XScale | Divide this number by 256 to obtain a width modifier when the texture is used as a scene flat. For example, -0.5 means the width should be reduced by half, and 0.5 means the width should be enlarged by half. 0.0 means no change. Always equal to YScale. |
26-27 | Int16 | YScale | Divide this number by 256 to obtain a height modifier when the texture is used as a scene flat. For example, -0.5 means the height should be reduced by half, and 0.5 means the height should be enlarged by half. 0.0 means no change. Always equal to XScale. |
UncompressedSingleFrame RecordsEdit
The majority of records follow this type. If the TextureRecord.Compression field is Uncompressed (or not within the defined range of the Compression enumeration), and the FrameCount is 1, then this algorithm defines the proper decoder.
The record defines a single frame image, but the image data is stored in a manner where it is interleaved with other records in 256 byte chunks. In order to successfully navigate the image data, one must read TextureRecord.Width bytes of pixel data (uncompressed, palette index values as per RCI files) and then skip ahead 256 - TextureRecord.Width
bytes from the present position. One must do this TextureRecord.Height times, once for each row of the image.
Example code:
for ( i = 0; i < Height; i++ ) { buffer = file.ReadBytes( Width ); file.Seek( 256 - Width, FromCurrentPosition ); }
Clearly this places a maximum width of Uncompressed images at 256 pixels.
UncompressedMultiframe RecordsEdit
The next most common subformat of records are decoded with this algorithm. If the Compression field is Uncompressed (or not within the defined range of the Compression enumeration) and the framecount is greater than 1, then this algorithm defines the proper decoder.
While the Compression field may indicate Uncompressed, there is a minor form of compression present. Fortunately the compression is fairly trivial to implement. Since the majority of images are bordered by several transparent pixels (palette index 0x00), a system of zero/non-zero alternations is used to read the row data for the record's image frames.
The data referenced by the TextureRecord.DataOffset field is a contiguous list of Int32 values, TextureRecord.FrameCount elements long. Each of these values is an offset relative to the TextureRecord.DataOffset field's value. Each of these offsets refers to a FrameData structure, which declares the dimensions of the animation frame.
FrameDataEdit
Offset | Type | Name | Description |
---|---|---|---|
0-1 | UInt16 | Width | The width of the frame. |
2-3 | UInt16 | Height | The height of the frame. |
4… | Int8 | PixelData | The variable-length pixel data for the frame. |
Reading UncompressedMultiframe RecordsEdit
So first one navigates to TextureRecord.DataOffset and reads a number of Int32 offsets, TextureRecord.FrameCount in count. At each offset a FrameData structure is defined. For each FrameData structure, one reads the FrameData.Width and FrameData.Height, and then reads a variable number of bytes according to the decoding algorithm.
Example code for decoding frames:
Int16 Width = file.ReadUInt16(); Int16 Height = file.ReadUInt16(); Byte[] row = new Byte[ Width ]; Int32 c = file.ReadByte(); Boolean isZero = true; Int32 p = 0; do { for ( i = 0; i < c; i++ ) { if ( true == isZero ) { row[ p++ ] = 0; } else { row[ p++ ] = file.ReadByte(); } } if ( ( p < Width ) || ( ( p == Width ) && ( true == isZero ) ) ) { c = file.ReadByte(); } isZero = !isZero; } while ( p < Width );
RecordRle RecordsEdit
This subformat is indicated by a value of RecordRle for the record's Compression field. The DataOffset field refers to a contiguous list of SpecialFrameHeader structure values, TextureRecord.FrameCount × TextureRecord.Height elements long. Since no RecordRle Records are known to have a TextureRecord.FrameCount other than 1, there should be exactly TextureRecord.Height elements.
After reading the list of SpecialFrameHeader structures, comes the trouble of decoding the data referenced by the SpecialFrameHeader.FrameOffset field. Depending on the SpecialFrameHeader.RowEncoding field's value, the data will either be read in a modified Rle format or it will be read as raw pixel data (as per RCI files). Each row of the frame is independent of all other rows of the frame, meaning the decision must be made on a row-by-row basis; One row might be compressed and the following row might be Uncompressed.
If the SpecialFrameHeader.RowEncoding field is equal to NotRleRowEncoded, one simply reads TextureRecord.Width bytes from the file as raw pixel data (Uncompressed palette index values). If the SpecialFrameHeader.RowEncoding field is equal to IsRleRowEncoded then one must rely on a modified Rle decoding routine for that row. Such row data is prefixed with the length of the row data, followed by the rle-compressed row data.
SpecialFrameHeaderEdit
Offset | Type | Name | Description |
---|---|---|---|
0-1 | Int16 | RowOffset | The offset to the actual frame-row data, relative to the record's DataOffset field. |
2-3 | RowEncoding (UInt16) | RowEncoding | This flag indicates that additional special handling is required. |
RowEncodingEdit
Value | Description | |
---|---|---|
0x8000 | IsRleEncoded | The row will require a modified form of Rle decompression to decode. |
0x0000 | NotRleEncoded | The row is not Rle compressed. |
Reading modified Rle row dataEdit
Example code for reading modified Rle row data:
Int32 w = file.ReadInt16(); Int32 p = 0; Int32 probe; Byte pixel; do { probe = file.ReadInt16(); if ( probe < 0 ) { probe = -probe; pixel = file.ReadByte(); for ( i = 0; i < probe; i++ ) { output[ p++ ] = pixel; } } else if ( 0 < probe ) { for ( i = 0; i < probe; i++ ) { output[ p++ ] = file.ReadByte(); } } } while ( p < w );
Reading RecordRle Records becomes something along the lines of:
foreach ( SpecialRowHeader header in specialFrameHeaderList ) { file.Seek( header.RowOffset ); if ( IsRleEncoded == header.RowEncoding ) { row = GetModifiedRleEncodedRow( file ); } else { row = file.ReadBytes( Width ); } }
ImageRle RecordsEdit
If the Compression field is ImageRle, then this algorithm defines the proper decoder. The DataOffset field refers to a contiguous list of SpecialFrameHeader structures, TextureRecord.FrameCount × TextureRecord.Height elements long. ImageRle Records are otherwise handled identically to RecordRle Records.
Special Texture FilesEdit
Note that there are 4 special texture files which are not described by the format and contain no images:
- TEXTURE.000
- TEXTURE.001
- These two texture files each contain 128 (0x80) images but with no TextureRecord or PixelData structures; just a TextureFileHeader and the TextureRecordHeaderList. The high byte of the RecordHeader.Unknown1 field assumedly holds the palette index for the color to use for the texture. The texture is used for any objects colored with a single, solid color rather than a bitmap. The file can be identified by an image offset of 0.
- TEXTURE.215
- TEXTURE.217
- Each of these textures contain one image but with only the TextureFileHeader and TextureRecordHeaderList list, but no further data. These files are most likely not used.