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