forked from jefetienne/daggerfall-unity
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGfxFile.cs
More file actions
316 lines (269 loc) · 9.92 KB
/
GfxFile.cs
File metadata and controls
316 lines (269 loc) · 9.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// Project: Daggerfall Tools For Unity
// Copyright: Copyright (C) 2009-2020 Daggerfall Workshop
// Web Site: http://www.dfworkshop.net
// License: MIT License (http://www.opensource.org/licenses/mit-license.php)
// Source Code: https://github.com/Interkarma/daggerfall-unity
// Original Author: Gavin Clayton (interkarma@dfworkshop.net)
// Contributors:
//
// Notes:
//
using System;
using System.IO;
using DaggerfallConnect.Utility;
namespace DaggerfallConnect.Arena2
{
/// <summary>
/// Connects to *.GFX files to extract image data.
/// The only GFX files in Daggerfall are SCRL00I0.GFX and SCRL01I0.GFX.
/// Combined thse make a total of 16 frames of scrolling parchment used exclusively by class questions UI.
/// </summary>
public class GfxFile : BaseImageFile
{
#region Fields
const int headerSize = 14;
Header header;
public DFBitmap[] frames;
#endregion
#region Properties
/// <summary>
/// Number of image records in this GFX file.
/// </summary>
public override int RecordCount
{
get
{
if (string.IsNullOrEmpty(managedFile.FilePath))
return 0;
else
return 1;
}
}
/// <summary>
/// Gets palette name for GFX file.
/// </summary>
public override string PaletteName
{
get { return "ART_PAL.COL"; }
}
/// <summary>
/// Description of this file (always "GFX File" as game data contain no text descriptions for this file type).
/// </summary>
public override string Description
{
get { return "GFX File"; }
}
#endregion
#region Structures
struct Header
{
public Int16 FrameCount; // Frame count, always 8
public Int16 Width; // Width of image, always 320
public Int16 Height; // Height of image, always 80
public Int16 PixelDataLength; // Always 25600 or 320*80
public Int16 Unknown2; // Always 1
public Byte[] Unknown3; // Always 4x zeros
}
struct Row
{
public UInt16 RowOffset; // Offset to data for this row from start of file
public RowEncoding RowEncoding; // Type of encoding used for this row
}
// Same row encoding value and RLE method used by TEXTURE files
enum RowEncoding
{
IsRleEncoded = 0x8000,
NotRleEncoded = 0,
}
#endregion
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
public GfxFile()
{
}
/// <summary>
/// Load constructor.
/// </summary>
/// <param name="filePath">Absolute path to *.GFX file.</param>
/// <param name="usage">Specify if file will be accessed from disk, or loaded into RAM.</param>
/// <param name="readOnly">File will be read-only if true, read-write if false.</param>
public GfxFile(string filePath, FileUsage usage, bool readOnly)
{
Load(filePath, usage, readOnly);
}
#endregion
#region Public Methods
/// <summary>
/// Loads a GFX file.
/// </summary>
/// <param name="filePath">Absolute path to *.GFX file.</param>
/// <param name="usage">Specify if file will be accessed from disk, or loaded into RAM.</param>
/// <param name="readOnly">File will be read-only if true, read-write if false.</param>
public override bool Load(string filePath, FileUsage usage, bool readOnly)
{
// Exit if this file already loaded
if (managedFile.FilePath == filePath)
return true;
// Validate filename
if (!filePath.EndsWith(".GFX", StringComparison.InvariantCultureIgnoreCase))
return false;
// Load file
if (!managedFile.Load(filePath, usage, readOnly))
return false;
// Read file
if (!Read())
return false;
return true;
}
#endregion
#region Overrides
/// <summary>
/// Gets number of frames.
/// </summary>
/// <param name="record">Record index. Only 1 record in Daggerfall GFX files so value should be 0.</param>
/// <returns>Number of frames.</returns>
public override int GetFrameCount(int record)
{
// Validate
if (record < 0 || record >= RecordCount)
return -1;
return header.FrameCount;
}
/// <summary>
/// Gets width and height of specified record. All frames of this record are the same dimensions.
/// </summary>
/// <param name="record">Index of record.</param>
/// <returns>DFSize object.</returns>
public override DFSize GetSize(int record)
{
// Validate
if (record < 0 || record >= RecordCount)
return new DFSize(0, 0);
return new DFSize(header.Width, header.Height);
}
/// <summary>
/// Gets bitmap data as indexed 8-bit byte array for specified record and frame.
/// </summary>
/// <param name="record">Index of record.</param>
/// <param name="frame">Index of frame.</param>
/// <returns>DFBitmap object.</returns>
public override DFBitmap GetDFBitmap(int record, int frame)
{
// Validate
if (record < 0 || record >= RecordCount || frame >= GetFrameCount(record))
return new DFBitmap();
return frames[frame];
}
#endregion
#region Readers
bool Read()
{
try
{
// Step through file
BinaryReader reader = managedFile.GetReader();
ReadHeader(reader);
ReadImageData(reader);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return false;
}
return true;
}
#endregion
#region Private Methods
// Read file header
void ReadHeader(BinaryReader reader)
{
reader.BaseStream.Position = 0;
header = new Header();
header.FrameCount = reader.ReadInt16();
header.Width = reader.ReadInt16();
header.Height = reader.ReadInt16();
header.PixelDataLength = reader.ReadInt16();
header.Unknown2 = reader.ReadInt16();
header.Unknown3 = reader.ReadBytes(4);
}
void ReadImageData(BinaryReader reader)
{
// Read image data for all rows after header
Row[] rows = new Row[header.Height * header.FrameCount];
for (int i = 0; i < header.Height * header.FrameCount; i++)
{
rows[i] = new Row()
{
RowOffset = reader.ReadUInt16(),
RowEncoding = (RowEncoding)reader.ReadUInt16(),
};
}
// Read all frames
frames = new DFBitmap[header.FrameCount];
for (int frame = 0; frame < header.FrameCount; frame++)
{
ReadRleImage(reader, rows, frame);
}
}
private bool ReadRleImage(BinaryReader reader, Row[] rows, int frame)
{
// Create buffer to hold extracted image
byte[] data = new byte[header.PixelDataLength];
frames[frame] = new DFBitmap();
frames[frame].Width = header.Width;
frames[frame].Height = header.Height;
frames[frame].Palette = Palette;
// Extract all rows of frame image
int dstPos = 0;
int rowStart = header.Height * frame;
for (int rowIndex = 0; rowIndex < header.Height; rowIndex++)
{
// Handle row data based on compression
Row row = rows[rowStart + rowIndex];
reader.BaseStream.Position = row.RowOffset;
if (row.RowEncoding == RowEncoding.IsRleEncoded)
{
// Extract RLE row
byte pixel = 0;
int probe = 0;
int rowPos = 0;
int rowWidth = reader.ReadUInt16();
do
{
probe = reader.ReadInt16();
if (probe < 0)
{
probe = -probe;
pixel = reader.ReadByte();
for (int i = 0; i < probe; i++)
{
data[dstPos++] = pixel;
rowPos++;
}
}
else if (0 < probe)
{
byte[] nextBytes = reader.ReadBytes(probe);
Array.Copy(nextBytes, 0, data, dstPos, nextBytes.Length);
dstPos += nextBytes.Length;
rowPos += probe;
}
} while (rowPos < rowWidth);
}
else
{
// Just copy bytes
byte[] nextBytes = reader.ReadBytes(header.Width);
Array.Copy(nextBytes, 0, data, dstPos, nextBytes.Length);
dstPos += nextBytes.Length;
}
}
// Assign complete data to image
frames[frame].Data = data;
return true;
}
#endregion
}
}