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