1 //-----------------------------------------------------------------------------
2 //
3 // ImageLib Sources
4 // Copyright (C) 2000-2009 by Denton Woods
5 // Last modified: 03/07/2009
6 //
7 // Filename: src-IL/src/il_utx.cpp
8 //
9 // Description: Reads from an Unreal and Unreal Tournament Texture (.utx) file.
10 // Specifications can be found at
11 // http://wiki.beyondunreal.com/Legacy:Package_File_Format.
12 //
13 //-----------------------------------------------------------------------------
14
15 #include "il_internal.h"
16 #ifndef IL_NO_UTX
17 #include "il_utx.h"
18
19
20 //! Reads a UTX file
ilLoadUtx(ILconst_string FileName)21 ILboolean ilLoadUtx(ILconst_string FileName)
22 {
23 ILHANDLE UtxFile;
24 ILboolean bUtx = IL_FALSE;
25
26 UtxFile = iopenr(FileName);
27 if (UtxFile == NULL) {
28 ilSetError(IL_COULD_NOT_OPEN_FILE);
29 return bUtx;
30 }
31
32 bUtx = ilLoadUtxF(UtxFile);
33 icloser(UtxFile);
34
35 return bUtx;
36 }
37
38
39 //! Reads an already-opened UTX file
ilLoadUtxF(ILHANDLE File)40 ILboolean ilLoadUtxF(ILHANDLE File)
41 {
42 ILuint FirstPos;
43 ILboolean bRet;
44
45 iSetInputFile(File);
46 FirstPos = itell();
47 try {
48 bRet = iLoadUtxInternal();
49 }
50 catch (bad_alloc &e) {
51 e;
52 ilSetError(IL_OUT_OF_MEMORY);
53 return IL_FALSE;
54 }
55 iseek(FirstPos, IL_SEEK_SET);
56
57 return bRet;
58 }
59
60
61 //! Reads from a memory "lump" that contains a UTX
ilLoadUtxL(const void * Lump,ILuint Size)62 ILboolean ilLoadUtxL(const void *Lump, ILuint Size)
63 {
64 try {
65 iSetInputLump(Lump, Size);
66 }
67 catch (bad_alloc &e) {
68 e;
69 ilSetError(IL_OUT_OF_MEMORY);
70 return IL_FALSE;
71 }
72 return iLoadUtxInternal();
73 }
74
75
GetUtxHead(UTXHEADER & Header)76 ILboolean GetUtxHead(UTXHEADER &Header)
77 {
78 Header.Signature = GetLittleUInt();
79 Header.Version = GetLittleUShort();
80 Header.LicenseMode = GetLittleUShort();
81 Header.Flags = GetLittleUInt();
82 Header.NameCount = GetLittleUInt();
83 Header.NameOffset = GetLittleUInt();
84 Header.ExportCount = GetLittleUInt();
85 Header.ExportOffset = GetLittleUInt();
86 Header.ImportCount = GetLittleUInt();
87 Header.ImportOffset = GetLittleUInt();
88
89 return IL_TRUE;
90 }
91
92
CheckUtxHead(UTXHEADER & Header)93 ILboolean CheckUtxHead(UTXHEADER &Header)
94 {
95 // This signature signifies a UTX file.
96 if (Header.Signature != 0x9E2A83C1)
97 return IL_FALSE;
98 // Unreal uses 61-63, and Unreal Tournament uses 67-69.
99 if ((Header.Version < 61 || Header.Version > 69))
100 return IL_FALSE;
101 return IL_TRUE;
102 }
103
104
105 // Gets a name variable from the file. Keep in mind that the return value must be freed.
GetUtxName(UTXHEADER & Header)106 string GetUtxName(UTXHEADER &Header)
107 {
108 #define NAME_MAX_LEN 256 //@TODO: Figure out if these can possibly be longer.
109 char Name[NAME_MAX_LEN];
110 ILubyte Length = 0;
111
112 // New style (Unreal Tournament) name. This has a byte at the beginning telling
113 // how long the string is (plus terminating 0), followed by the terminating 0.
114 if (Header.Version >= 64) {
115 Length = igetc();
116 if (iread(Name, Length, 1) != 1)
117 return "";
118 if (Name[Length-1] != 0)
119 return "";
120 return string(Name);
121 }
122
123 // Old style (Unreal) name. This string length is unknown, but it is terminated
124 // by a 0.
125 do {
126 Name[Length++] = igetc();
127 } while (!ieof() && Name[Length-1] != 0 && Length < NAME_MAX_LEN);
128
129 // Never reached the terminating 0.
130 if (Length == NAME_MAX_LEN && Name[Length-1] != 0)
131 return "";
132
133 return string(Name);
134 #undef NAME_MAX_LEN
135 }
136
137
GetUtxNameTable(vector<UTXENTRYNAME> & NameEntries,UTXHEADER & Header)138 bool GetUtxNameTable(vector <UTXENTRYNAME> &NameEntries, UTXHEADER &Header)
139 {
140 ILuint NumRead;
141
142 // Go to the name table.
143 iseek(Header.NameOffset, IL_SEEK_SET);
144
145 NameEntries.resize(Header.NameCount);
146
147 // Read in the name table.
148 for (NumRead = 0; NumRead < Header.NameCount; NumRead++) {
149 NameEntries[NumRead].Name = GetUtxName(Header);
150 if (NameEntries[NumRead].Name == "")
151 break;
152 NameEntries[NumRead].Flags = GetLittleUInt();
153 }
154
155 // Did not read all of the entries (most likely GetUtxName failed).
156 if (NumRead < Header.NameCount) {
157 ilSetError(IL_INVALID_FILE_HEADER);
158 return false;
159 }
160
161 return true;
162 }
163
164
165 // This following code is from http://wiki.beyondunreal.com/Legacy:Package_File_Format/Data_Details.
166 /// <summary>Reads a compact integer from the FileReader.
167 /// Bytes read differs, so do not make assumptions about
168 /// physical data being read from the stream. (If you have
169 /// to, get the difference of FileReader.BaseStream.Position
170 /// before and after this is executed.)</summary>
171 /// <returns>An "uncompacted" signed integer.</returns>
172 /// <remarks>FileReader is a System.IO.BinaryReader mapped
173 /// to a file. Also, there may be better ways to implement
174 /// this, but this is fast, and it works.</remarks>
UtxReadCompactInteger()175 ILint UtxReadCompactInteger()
176 {
177 int output = 0;
178 ILboolean sign = IL_FALSE;
179 int i;
180 ILubyte x;
181 for(i = 0; i < 5; i++)
182 {
183 x = igetc();
184 // First byte
185 if(i == 0)
186 {
187 // Bit: X0000000
188 if((x & 0x80) > 0)
189 sign = IL_TRUE;
190 // Bits: 00XXXXXX
191 output |= (x & 0x3F);
192 // Bit: 0X000000
193 if((x & 0x40) == 0)
194 break;
195 }
196 // Last byte
197 else if(i == 4)
198 {
199 // Bits: 000XXXXX -- the 0 bits are ignored
200 // (hits the 32 bit boundary)
201 output |= (x & 0x1F) << (6 + (3 * 7));
202 }
203 // Middle bytes
204 else
205 {
206 // Bits: 0XXXXXXX
207 output |= (x & 0x7F) << (6 + ((i - 1) * 7));
208 // Bit: X0000000
209 if((x & 0x80) == 0)
210 break;
211 }
212 }
213 // multiply by negative one here, since the first 6+ bits could be 0
214 if (sign)
215 output *= -1;
216 return output;
217 }
218
219
ChangeObjectReference(ILint * ObjRef,ILboolean * IsImported)220 void ChangeObjectReference(ILint *ObjRef, ILboolean *IsImported)
221 {
222 if (*ObjRef < 0) {
223 *IsImported = IL_TRUE;
224 *ObjRef = -*ObjRef - 1;
225 }
226 else if (*ObjRef > 0) {
227 *IsImported = IL_FALSE;
228 *ObjRef = *ObjRef - 1; // This is an object reference, so we have to do this conversion.
229 }
230 else {
231 *ObjRef = -1; // "NULL" pointer
232 }
233
234 return;
235 }
236
237
GetUtxExportTable(vector<UTXEXPORTTABLE> & ExportTable,UTXHEADER & Header)238 bool GetUtxExportTable(vector <UTXEXPORTTABLE> &ExportTable, UTXHEADER &Header)
239 {
240 ILuint i;
241
242 // Go to the name table.
243 iseek(Header.ExportOffset, IL_SEEK_SET);
244
245 // Create ExportCount elements in our array.
246 ExportTable.resize(Header.ExportCount);
247
248 for (i = 0; i < Header.ExportCount; i++) {
249 ExportTable[i].Class = UtxReadCompactInteger();
250 ExportTable[i].Super = UtxReadCompactInteger();
251 ExportTable[i].Group = GetLittleUInt();
252 ExportTable[i].ObjectName = UtxReadCompactInteger();
253 ExportTable[i].ObjectFlags = GetLittleUInt();
254 ExportTable[i].SerialSize = UtxReadCompactInteger();
255 ExportTable[i].SerialOffset = UtxReadCompactInteger();
256
257 ChangeObjectReference(&ExportTable[i].Class, &ExportTable[i].ClassImported);
258 ChangeObjectReference(&ExportTable[i].Super, &ExportTable[i].SuperImported);
259 ChangeObjectReference(&ExportTable[i].Group, &ExportTable[i].GroupImported);
260 }
261
262 return true;
263 }
264
265
GetUtxImportTable(vector<UTXIMPORTTABLE> & ImportTable,UTXHEADER & Header)266 bool GetUtxImportTable(vector <UTXIMPORTTABLE> &ImportTable, UTXHEADER &Header)
267 {
268 ILuint i;
269
270 // Go to the name table.
271 iseek(Header.ImportOffset, IL_SEEK_SET);
272
273 // Allocate the name table.
274 ImportTable.resize(Header.ImportCount);
275
276 for (i = 0; i < Header.ImportCount; i++) {
277 ImportTable[i].ClassPackage = UtxReadCompactInteger();
278 ImportTable[i].ClassName = UtxReadCompactInteger();
279 ImportTable[i].Package = GetLittleUInt();
280 ImportTable[i].ObjectName = UtxReadCompactInteger();
281
282 ChangeObjectReference(&ImportTable[i].Package, &ImportTable[i].PackageImported);
283 }
284
285 return true;
286 }
287
288
289 /*void UtxDestroyPalettes(UTXPALETTE *Palettes, ILuint NumPal)
290 {
291 ILuint i;
292 for (i = 0; i < NumPal; i++) {
293 //ifree(Palettes[i].Name);
294 ifree(Palettes[i].Pal);
295 }
296 ifree(Palettes);
297 }*/
298
299
UtxFormatToDevIL(ILuint Format)300 ILenum UtxFormatToDevIL(ILuint Format)
301 {
302 switch (Format)
303 {
304 case UTX_P8:
305 return IL_COLOR_INDEX;
306 case UTX_DXT1:
307 return IL_RGBA;
308 }
309 return IL_BGRA; // Should never reach here.
310 }
311
312
UtxFormatToBpp(ILuint Format)313 ILuint UtxFormatToBpp(ILuint Format)
314 {
315 switch (Format)
316 {
317 case UTX_P8:
318 return 1;
319 case UTX_DXT1:
320 return 4;
321 }
322 return 4; // Should never reach here.
323 }
324
325
326 // Internal function used to load the UTX.
iLoadUtxInternal(void)327 ILboolean iLoadUtxInternal(void)
328 {
329 UTXHEADER Header;
330 vector <UTXENTRYNAME> NameEntries;
331 vector <UTXEXPORTTABLE> ExportTable;
332 vector <UTXIMPORTTABLE> ImportTable;
333 vector <UTXPALETTE> Palettes;
334 ILimage *Image;
335 ILuint NumPal = 0, i, j = 0;
336 ILint Name;
337 ILubyte Type;
338 ILint Val;
339 ILint Size;
340 ILint Width, Height, PalEntry;
341 ILboolean BaseCreated = IL_FALSE, HasPal;
342 ILint Format;
343 ILubyte *CompData = NULL;
344
345 if (iCurImage == NULL) {
346 ilSetError(IL_ILLEGAL_OPERATION);
347 return IL_FALSE;
348 }
349
350 if (!GetUtxHead(Header))
351 return IL_FALSE;
352 if (!CheckUtxHead(Header))
353 return IL_FALSE;
354
355 // We grab the name table.
356 if (!GetUtxNameTable(NameEntries, Header))
357 return IL_FALSE;
358 // Then we get the export table.
359 if (!GetUtxExportTable(ExportTable, Header))
360 return IL_FALSE;
361 // Then the last table is the import table.
362 if (!GetUtxImportTable(ImportTable, Header))
363 return IL_FALSE;
364
365 // Find the number of palettes in the export table.
366 for (i = 0; i < Header.ExportCount; i++) {
367 if (NameEntries[ImportTable[ExportTable[i].Class].ObjectName].Name == "Palette")
368 NumPal++;
369 }
370 Palettes.resize(NumPal);
371
372 // Read in all of the palettes.
373 NumPal = 0;
374 for (i = 0; i < Header.ExportCount; i++) {
375 if (NameEntries[ImportTable[ExportTable[i].Class].ObjectName].Name == "Palette") {
376 Palettes[NumPal].Name = i;
377 iseek(ExportTable[NumPal].SerialOffset, IL_SEEK_SET);
378 Name = igetc(); // Skip the 2. @TODO: Can there be more in front of the palettes?
379 Palettes[NumPal].Count = UtxReadCompactInteger();
380 Palettes[NumPal].Pal = new ILubyte[Palettes[NumPal].Count * 4];
381 if (iread(Palettes[NumPal].Pal, Palettes[NumPal].Count * 4, 1) != 1)
382 return IL_FALSE;
383 NumPal++;
384 }
385 }
386
387 for (i = 0; i < Header.ExportCount; i++) {
388 // Find textures in the file.
389 if (NameEntries[ImportTable[ExportTable[i].Class].ObjectName].Name == "Texture") {
390 iseek(ExportTable[i].SerialOffset, IL_SEEK_SET);
391 Width = -1; Height = -1; PalEntry = NumPal; HasPal = IL_FALSE; Format = -1;
392
393 do {
394 // Always starts with a comptact integer that gives us an entry into the name table.
395 Name = UtxReadCompactInteger();
396 if (NameEntries[Name].Name == "None")
397 break;
398 Type = igetc();
399 Size = (Type & 0x70) >> 4;
400
401 if (Type == 0xA2)
402 igetc(); // Byte is 1 here...
403
404 switch (Type & 0x0F)
405 {
406 case 1: // Get a single byte.
407 Val = igetc();
408 break;
409
410 case 2: // Get unsigned integer.
411 Val = GetLittleUInt();
412 break;
413
414 case 3: // Boolean value is in the info byte.
415 igetc();
416 break;
417
418 case 4: // Skip flaots for right now.
419 GetLittleFloat();
420 break;
421
422 case 5:
423 case 6: // Get a compact integer - an object reference.
424 Val = UtxReadCompactInteger();
425 Val--;
426 break;
427
428 case 10:
429 Val = igetc();
430 switch (Size)
431 {
432 case 0:
433 iseek(1, IL_SEEK_CUR);
434 break;
435 case 1:
436 iseek(2, IL_SEEK_CUR);
437 break;
438 case 2:
439 iseek(4, IL_SEEK_CUR);
440 break;
441 case 3:
442 iseek(12, IL_SEEK_CUR);
443 break;
444 }
445 break;
446
447 default: // Uhm...
448 break;
449 }
450
451 //@TODO: What should we do if Name >= Header.NameCount?
452 if ((ILuint)Name < Header.NameCount) { // Don't want to go past the end of our array.
453 if (NameEntries[Name].Name == "Palette") {
454 // If it has references to more than one palette, just use the first one.
455 if (HasPal == IL_FALSE) {
456 // We go through the palette list here to match names.
457 for (PalEntry = 0; (ILuint)PalEntry < NumPal; PalEntry++) {
458 if (Val == Palettes[PalEntry].Name) {
459 HasPal = IL_TRUE;
460 break;
461 }
462 }
463 }
464 }
465 if (NameEntries[Name].Name == "Format") // Not required for P8 images but can be present.
466 if (Format == -1)
467 Format = Val;
468 if (NameEntries[Name].Name == "USize") // Width of the image
469 if (Width == -1)
470 Width = Val;
471 if (NameEntries[Name].Name == "VSize") // Height of the image
472 if (Height == -1)
473 Height = Val;
474 }
475 } while (!ieof());
476
477 // If the format property is not present, it is a paletted (P8) image.
478 if (Format == -1)
479 Format = UTX_P8;
480 // Just checks for everything being proper. If the format is P8, we check to make sure that a palette was found.
481 if (Width == -1 || Height == -1 || (PalEntry == NumPal && Format != UTX_DXT1) || (Format != UTX_P8 && Format != UTX_DXT1))
482 return IL_FALSE;
483 if (BaseCreated == IL_FALSE) {
484 BaseCreated = IL_TRUE;
485 ilTexImage(Width, Height, 1, UtxFormatToBpp(Format), UtxFormatToDevIL(Format), IL_UNSIGNED_BYTE, NULL);
486 Image = iCurImage;
487 }
488 else {
489 Image->Next = ilNewImageFull(Width, Height, 1, UtxFormatToBpp(Format), UtxFormatToDevIL(Format), IL_UNSIGNED_BYTE, NULL);
490 if (Image->Next == NULL)
491 return IL_FALSE;
492 Image = Image->Next;
493 }
494
495 // Skip the mipmap count, width offset and mipmap size entries.
496 //@TODO: Implement mipmaps.
497 iseek(5, IL_SEEK_CUR);
498 UtxReadCompactInteger();
499
500 switch (Format)
501 {
502 case UTX_P8:
503 Image->Pal.PalSize = Palettes[PalEntry].Count * 4;
504 Image->Pal.Palette = (ILubyte*)ialloc(Image->Pal.PalSize);
505 if (Image->Pal.Palette == NULL)
506 return IL_FALSE;
507 memcpy(Image->Pal.Palette, Palettes[PalEntry].Pal, Image->Pal.PalSize);
508 Image->Pal.PalType = IL_PAL_RGBA32;
509
510 if (iread(Image->Data, Image->SizeOfData, 1) != 1)
511 return IL_FALSE;
512 break;
513
514 case UTX_DXT1:
515 Image->DxtcSize = IL_MAX(Image->Width * Image->Height / 2, 8);
516 CompData = (ILubyte*)ialloc(Image->DxtcSize);
517 if (CompData == NULL)
518 return IL_FALSE;
519
520 if (iread(CompData, Image->DxtcSize, 1) != 1) {
521 ifree(CompData);
522 return IL_FALSE;
523 }
524 // Keep a copy of the DXTC data if the user wants it.
525 if (ilGetInteger(IL_KEEP_DXTC_DATA) == IL_TRUE) {
526 Image->DxtcData = CompData;
527 Image->DxtcFormat = IL_DXT1;
528 CompData = NULL;
529 }
530 if (DecompressDXT1(Image, CompData) == IL_FALSE) {
531 ifree(CompData);
532 return IL_FALSE;
533 }
534 ifree(CompData);
535 break;
536 }
537 Image->Origin = IL_ORIGIN_UPPER_LEFT;
538 }
539 }
540
541 return ilFixImage();
542 }
543
544 #endif//IL_NO_UTX
545
546