1#include <stdio.h>
  2#include <stdlib.h>
  3#include <stdint.h>
  4#include <string.h>
  5#include <stdbool.h>
  6#include <stdarg.h>
  7#include <getopt.h>
  8#include <libgen.h>
  9
 10#define STB_IMAGE_WRITE_IMPLEMENTATION
 11#include "stb_image_write.h"
 12
 13struct blp2header {
 14	uint8_t  ident[4];             // "BLP2" magic number
 15	uint32_t type;                 // 0 = JPG, 1 = BLP / DXTC / Uncompressed
 16	uint8_t  compression;          // 1 = BLP, 2 = DXTC, 3 = Uncompressed
 17	uint8_t  alpha_depth;          // 0, 1, 4, or 8
 18	uint8_t  alpha_type;           // 0, 1, 7, or 8
 19	uint8_t  has_mips;             // 0 = no mips, 1 = has mips
 20	uint32_t width;                // Image width in pixels
 21	uint32_t height;               // Image height in pixels
 22	uint32_t mipmap_offsets[16];   // File offsets of each mipmap
 23	uint32_t mipmap_lengths[16];   // Length of each mipmap data block
 24	uint32_t palette[256];         // Color palette (256 ARGB values)
 25} __attribute__((packed));         // Ensure no padding issues
 26
 27typedef struct {
 28	const char *fullname;
 29	char *folder;
 30	char *filename;                // Filename without extension
 31	char *extension;               // File extension (including the dot)
 32} path_components;
 33
 34const char *type_labels[] = {
 35	"JPG",                         // Index 0
 36	"BLP/DXTC/Uncompressed"        // Index 1
 37};
 38
 39const char *compression_labels[] = {
 40	"Invalid",                     // Index 0 (Unused)
 41	"BLP",                         // Index 1
 42	"DXTC",                        // Index 2
 43	"Uncompressed"                 // Index 3
 44};
 45
 46typedef enum {
 47	PNG,
 48	BMP,
 49	TGA,
 50	JPG,
 51} image_format;
 52
 53image_format get_format_type(const char *format) {
 54	if (strcasecmp(format, "png") == 0) return PNG;
 55	if (strcasecmp(format, "bmp") == 0) return BMP;
 56	if (strcasecmp(format, "tga") == 0) return TGA;
 57	if (strcasecmp(format, "jpg") == 0) return JPG;
 58	return PNG;
 59}
 60
 61bool is_valid_format(const char *format) {
 62	const char *valid_formats[] = {"png", "bmp", "tga", "jpg", NULL};
 63	for (const char **f = valid_formats; *f != NULL; f++) {
 64		if (strcasecmp(format, *f) == 0) {
 65			return true;
 66		}
 67	}
 68	return false;
 69}
 70
 71path_components extract_path_components(const char *filepath) {
 72	path_components result;
 73	result.fullname = filepath;
 74	char *filepath_copy1 = strdup(filepath);
 75	char *filepath_copy2 = strdup(filepath);
 76
 77	result.folder = strdup(dirname(filepath_copy1));
 78
 79	char *full_filename = basename(filepath_copy2);
 80	char *last_dot = strrchr(full_filename, '.');
 81
 82	if (last_dot != NULL && last_dot != full_filename) {
 83		size_t name_length = last_dot - full_filename;
 84		result.filename = (char *)malloc(name_length + 1);
 85		strncpy(result.filename, full_filename, name_length);
 86		result.filename[name_length] = '\0';
 87		result.extension = strdup(last_dot);
 88	} else {
 89		result.filename = strdup(full_filename);
 90		result.extension = strdup("");
 91	}
 92
 93	free(filepath_copy1);
 94	free(filepath_copy2);
 95
 96	return result;
 97}
 98
 99void free_path_components(path_components *path) {
100	free(path->folder);
101	free(path->filename);
102	free(path->extension);
103}
104
105void dxt1_to_rgba(const uint8_t *dxt1_block, uint8_t *rgba_pixels) {
106	uint16_t color0 = (dxt1_block[0] | (dxt1_block[1] << 8));
107	uint16_t color1 = (dxt1_block[2] | (dxt1_block[3] << 8));
108	uint32_t color_bits = (dxt1_block[4] | (dxt1_block[5] << 8) | (dxt1_block[6] << 16) | (dxt1_block[7] << 24));
109
110	uint8_t r0 = ((color0 >> 11) & 0x1F) << 3;
111	uint8_t g0 = ((color0 >> 5) & 0x3F) << 2;
112	uint8_t b0 = (color0 & 0x1F) << 3;
113
114	uint8_t r1 = ((color1 >> 11) & 0x1F) << 3;
115	uint8_t g1 = ((color1 >> 5) & 0x3F) << 2;
116	uint8_t b1 = (color1 & 0x1F) << 3;
117
118	uint8_t colors[4][4];
119
120	colors[0][0] = r0; colors[0][1] = g0; colors[0][2] = b0; colors[0][3] = 255;
121	colors[1][0] = r1; colors[1][1] = g1; colors[1][2] = b1; colors[1][3] = 255;
122
123	if (color0 > color1) {
124		colors[2][0] = (2 * r0 + r1) / 3;
125		colors[2][1] = (2 * g0 + g1) / 3;
126		colors[2][2] = (2 * b0 + b1) / 3;
127		colors[2][3] = 255;
128
129		colors[3][0] = (r0 + 2 * r1) / 3;
130		colors[3][1] = (g0 + 2 * g1) / 3;
131		colors[3][2] = (b0 + 2 * b1) / 3;
132		colors[3][3] = 255;
133	} else {
134		colors[2][0] = (r0 + r1) / 2;
135		colors[2][1] = (g0 + g1) / 2;
136		colors[2][2] = (b0 + b1) / 2;
137		colors[2][3] = 255;
138
139		colors[3][0] = 0;
140		colors[3][1] = 0;
141		colors[3][2] = 0;
142		colors[3][3] = 0;
143	}
144
145	for (int i = 0; i < 16; i++) {
146		uint8_t color_idx = (color_bits >> (i * 2)) & 0x3;
147		uint8_t* pixel = rgba_pixels + (i * 4);
148		pixel[0] = colors[color_idx][0];
149		pixel[1] = colors[color_idx][1];
150		pixel[2] = colors[color_idx][2];
151		pixel[3] = colors[color_idx][3];
152	}
153}
154
155void dxt3_to_rgba(const uint8_t *dxt3_block, uint8_t *rgba_pixels) {
156	// First 8 bytes contain the alpha values (4 bits per pixel).
157	uint64_t alpha_bits;
158	memcpy(&alpha_bits, dxt3_block, 8);
159
160	// Last 8 bytes contain the color data (same as DXT1 without alpha interpretation).
161	dxt1_to_rgba(dxt3_block + 8, rgba_pixels);
162
163	// Override the alpha values with the explicit values from the alpha block.
164	for (int i = 0; i < 16; i++) {
165		uint8_t alpha = ((alpha_bits >> (i * 4)) & 0xF) << 4 | ((alpha_bits >> (i * 4)) & 0xF);
166		rgba_pixels[i * 4 + 3] = alpha;  // Set the alpha channel.
167	}
168}
169
170void dxt5_to_rgba(const uint8_t *dxt5_block, uint8_t *rgba_pixels) {
171	// First 8 bytes contain the interpolated alpha values.
172	uint8_t alpha0 = dxt5_block[0];
173	uint8_t alpha1 = dxt5_block[1];
174
175	// Read the 6 bytes of alpha indices (48 bits total, 3 bits per pixel).
176	uint64_t alpha_indices = 0;
177	memcpy(&alpha_indices, dxt5_block + 2, 6);
178
179	// Calculate alpha values table.
180	uint8_t alpha_table[8];
181	alpha_table[0] = alpha0;
182	alpha_table[1] = alpha1;
183
184	if (alpha0 > alpha1) {
185		// 8-alpha interpolation.
186		alpha_table[2] = (6 * alpha0 + 1 * alpha1) / 7;
187		alpha_table[3] = (5 * alpha0 + 2 * alpha1) / 7;
188		alpha_table[4] = (4 * alpha0 + 3 * alpha1) / 7;
189		alpha_table[5] = (3 * alpha0 + 4 * alpha1) / 7;
190		alpha_table[6] = (2 * alpha0 + 5 * alpha1) / 7;
191		alpha_table[7] = (1 * alpha0 + 6 * alpha1) / 7;
192	} else {
193		// 6-alpha interpolation.
194		alpha_table[2] = (4 * alpha0 + 1 * alpha1) / 5;
195		alpha_table[3] = (3 * alpha0 + 2 * alpha1) / 5;
196		alpha_table[4] = (2 * alpha0 + 3 * alpha1) / 5;
197		alpha_table[5] = (1 * alpha0 + 4 * alpha1) / 5;
198		alpha_table[6] = 0;
199		alpha_table[7] = 255;
200	}
201
202	// Decode the color data (last 8 bytes).
203	dxt1_to_rgba(dxt5_block + 8, rgba_pixels);
204
205	// Apply alpha values.
206	for (int i = 0; i < 16; i++) {
207		// Extract 3-bit alpha index.
208		int bit_pos = i * 3;
209		int byte_pos = bit_pos / 8;
210		int bit_offset = bit_pos % 8;
211
212		uint8_t alpha_index;
213		if (bit_offset > 5) {
214			// Alpha index spans two bytes.
215			alpha_index = (dxt5_block[2 + byte_pos] >> bit_offset) | ((dxt5_block[2 + byte_pos + 1] & ((1 << (bit_offset - 5)) - 1)) << (8 - bit_offset));
216		} else {
217			alpha_index = (dxt5_block[2 + byte_pos] >> bit_offset) & 0x07;
218		}
219
220		rgba_pixels[i * 4 + 3] = alpha_table[alpha_index];
221	}
222}
223
224void decode_dxt_image(const uint8_t *image_data, uint32_t width, uint32_t height, int dxt_type, path_components *path, bool verbose, char *format) {
225	uint32_t blocks_wide = (width + 3) / 4;
226	uint32_t blocks_high = (height + 3) / 4;
227	uint32_t total_pixels = width * height;
228	uint32_t block_size = (dxt_type == 1) ? 8 : 16; // DXT3/5 blocks are 16 bytes, DXT1 are 8 bytes.
229
230	uint8_t* decoded_image = (uint8_t*)malloc(total_pixels * 4);
231	if (!decoded_image) {
232		printf("Failed to allocate memory for decoded image\n");
233		return;
234	}
235
236	// Process each block.
237	for (uint32_t by = 0; by < blocks_high; by++) {
238		for (uint32_t bx = 0; bx < blocks_wide; bx++) {
239			uint8_t block_rgba[64];
240			const uint8_t *dxt_block = image_data + (by * blocks_wide + bx) * block_size;
241
242			switch (dxt_type) {
243				case 1:
244					dxt1_to_rgba(dxt_block, block_rgba);
245					break;
246				case 3:
247					dxt3_to_rgba(dxt_block, block_rgba);
248					break;
249				case 5:
250					dxt5_to_rgba(dxt_block, block_rgba);
251					break;
252			}
253
254			for (int py = 0; py < 4; py++) {
255				for (int px = 0; px < 4; px++) {
256					int x = bx * 4 + px;
257					int y = by * 4 + py;
258
259					if (x >= width || y >= height) continue;
260
261					int src_idx = (py * 4 + px) * 4;
262					int dst_idx = (y * width + x) * 4;
263
264					decoded_image[dst_idx + 0] = block_rgba[src_idx + 0];
265					decoded_image[dst_idx + 1] = block_rgba[src_idx + 1];
266					decoded_image[dst_idx + 2] = block_rgba[src_idx + 2];
267					decoded_image[dst_idx + 3] = block_rgba[src_idx + 3];
268				}
269			}
270		}
271	}
272
273	if (verbose) {
274		printf("Saving decoded image...\n");
275	}
276
277	char output_filename[512];
278	snprintf(output_filename, sizeof(output_filename), "%s/%s.%s", path->folder, path->filename, format);
279
280	image_format fmt = get_format_type(format);
281	switch (fmt) {
282		case PNG:
283			if (verbose) printf("Processing as PNG format\n");
284			if (stbi_write_png(output_filename, width, height, 4, decoded_image, width * 4) == 0) {
285				printf("Failed to write %s file\n", output_filename);
286			} else {
287				printf("Successfully saved %s\n", output_filename);
288			}
289			break;
290		case BMP:
291			if (verbose) printf("Processing as BMP format\n");
292			if (stbi_write_bmp(output_filename, width, height, 4, decoded_image) == 0) {
293				printf("Failed to write %s file\n", output_filename);
294			} else {
295				printf("Successfully saved %s\n", output_filename);
296			}
297			break;
298		case TGA:
299			if (verbose) printf("Processing as TGA format\n");
300			if (stbi_write_tga(output_filename, width, height, 4, decoded_image) == 0) {
301				printf("Failed to write %s file\n", output_filename);
302			} else {
303				printf("Successfully saved %s\n", output_filename);
304			}
305			break;
306		case JPG:
307			if (verbose) printf("Processing as JPG format\n");
308			if (stbi_write_jpg(output_filename, width, height, 4, decoded_image, 100) == 0) {
309				printf("Failed to write %s file\n", output_filename);
310			} else {
311				printf("Successfully saved %s\n", output_filename);
312			}
313			break;
314	}
315
316	// Print first few pixels for verification.
317	if (verbose) {
318		printf("\nFirst few pixels of decoded image (RGBA format):\n");
319		for (int y = 0; y < 4; y++) {
320			for (int x = 0; x < 4; x++) {
321				int idx = (y * width + x) * 4;
322				printf("(%3d,%3d,%3d,%3d) ", 
323						decoded_image[idx],
324						decoded_image[idx + 1],
325						decoded_image[idx + 2],
326						decoded_image[idx + 3]);
327			}
328			printf("\n");
329		}
330	}
331
332	free(decoded_image);
333}
334
335void convert_blp_file(path_components *path, bool verbose, char *format) {
336	FILE *file = fopen(path->fullname, "rb");
337	if (!file) {
338		perror("Error opening file");
339		return;
340	}
341
342	struct blp2header header;
343
344	if (fread(&header, sizeof(struct blp2header), 1, file) != 1) {
345		perror("Error reading header");
346		fclose(file);
347		return;
348	}
349
350	if (memcmp(header.ident, "BLP2", 4) != 0) {
351		printf("Invalid BLP file!\n");
352		fclose(file);
353		return;
354	}
355
356	if (verbose) {
357		printf("BLP File Details:\n");
358		printf("  Type: %u, %s\n", header.type, type_labels[header.type]);
359		printf("  Compression: %u, %s\n", header.compression, compression_labels[header.compression]);
360		printf("  Alpha Depth: %u\n", header.alpha_depth);
361		printf("  Alpha Type: %u\n", header.alpha_type);
362		printf("  Has Mipmaps: %u\n", header.has_mips);
363		printf("  Width: %u, Height: %u\n", header.width, header.height);
364	}
365
366	// Determine image data location.
367	uint32_t offset = header.mipmap_offsets[0]; // First mipmap (highest resolution)
368	uint32_t length = header.mipmap_lengths[0]; // Length of the mipmap
369
370	if (offset == 0 || length == 0) {
371		printf("No image data found.\n");
372		fclose(file);
373		return;
374	}
375
376	if (verbose) {
377		printf("Reading image data at offset %u, size %u bytes\n", offset, length);
378	}
379
380	// Allocate buffer and read image data.
381	uint8_t *image_data = (uint8_t *)malloc(length);
382	if (!image_data) {
383		perror("Memory allocation failed");
384		fclose(file);
385		return;
386	}
387
388	fseek(file, offset, SEEK_SET);
389	if (fread(image_data, length, 1, file) != 1) {
390		perror("Error reading image data");
391		free(image_data);
392		fclose(file);
393		return;
394	}
395
396	if (header.compression == 2) {
397		if (verbose) {
398			printf("BLP is compressed with DXTC.\n");
399			printf("Image has %d bytes.\n", length);
400		}
401
402		switch (header.alpha_type) {
403			case 0: // DXT1
404				decode_dxt_image(image_data, header.width, header.height, 1, path, verbose, format);
405				break;
406			case 1: // DXT3
407				decode_dxt_image(image_data, header.width, header.height, 3, path, verbose, format);
408				break;
409			case 7: // DXT5
410				decode_dxt_image(image_data, header.width, header.height, 5, path, verbose, format);
411				break;
412			default:
413				printf("Unsupported alpha type: %d\n", header.alpha_type);
414				break;
415		}
416
417	}
418
419	fclose(file);
420}
421
422void print_help(const char *program_name) {
423	printf("Usage: %s [OPTIONS] file1 [file2 ...]\n\n", program_name);
424	printf("Options:\n");
425	printf("  -h, --help           Display this help message\n");
426	printf("  -v, --verbose        Enable verbose output\n");
427	printf("  -f, --format=FORMAT  Set output format (default: png)\n");
428	printf("                       Options: png, bmp, tga, jpg\n");
429}
430
431int main(int argc, char *argv[]) {
432	bool verbose = false;
433	char *format = "png";
434	int c;
435
436	static struct option long_options[] = {
437		{"format",  required_argument, 0, 'f'},
438		{"help",    no_argument, 0, 'h'},
439		{"verbose", no_argument, 0, 'v'},
440		{0, 0, 0, 0}
441	};
442
443	// Parse command line options
444	while (1) {
445		int option_index = 0;
446		c = getopt_long(argc, argv, "f:hv", long_options, &option_index);
447		if (c == -1) { break; }
448
449		switch (c) {
450			case 'h':
451				print_help(argv[0]);
452				return 0;
453			case 'v':
454				verbose = true;
455				break;
456			case 'f':
457				if (!is_valid_format(optarg)) {
458					fprintf(stderr, "Error: Invalid format '%s'. Valid formats are: png, bmp, tga, jpg\n", optarg);
459					return 1;
460				}
461				format = optarg;
462				break;
463			case '?':
464				return 1;
465			default:
466				abort();
467		}
468	}
469
470	// No files specified
471	if (optind >= argc) {
472		fprintf(stderr, "Error: No input files specified\n");
473		print_help(argv[0]);
474		return 1;
475	}
476
477	// Loop though all provided files.
478	while (optind < argc) {
479		path_components path = extract_path_components(argv[optind++]);
480
481		if (verbose) {
482			printf("Processing File:\n");
483			printf("  Fullname: %s\n", path.fullname);
484			printf("  Folder: %s\n", path.folder);
485			printf("  Filename: %s\n", path.filename);
486			printf("  Extension: %s\n", path.extension);
487			printf("  Format: %s\n", format);
488		}
489
490		convert_blp_file(&path, verbose, format);
491		free_path_components(&path);
492	}
493
494	return 0;
495}
496