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