sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

n64graphics_ci.c (17170B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <strings.h>
      4 
      5 #define STBI_NO_LINEAR
      6 #define STBI_NO_HDR
      7 #define STBI_NO_TGA
      8 #define STB_IMAGE_IMPLEMENTATION
      9 #include "../stb/stb_image.h"
     10 #define STB_IMAGE_WRITE_IMPLEMENTATION
     11 #include "../stb/stb_image_write.h"
     12 
     13 #include "exoquant/exoquant.h"
     14 
     15 #include "n64graphics_ci.h"
     16 #include "utils.h"
     17 
     18 // SCALE_M_N: upscale/downscale M-bit integer to N-bit
     19 #define SCALE_5_8(VAL_) (((VAL_) * 0xFF) / 0x1F)
     20 #define SCALE_8_5(VAL_) ((((VAL_) + 4) * 0x1F) / 0xFF)
     21 #define SCALE_4_8(VAL_) ((VAL_) * 0x11)
     22 #define SCALE_8_4(VAL_) ((VAL_) / 0x11)
     23 #define SCALE_3_8(VAL_) ((VAL_) * 0x24)
     24 #define SCALE_8_3(VAL_) ((VAL_) / 0x24)
     25 
     26 typedef enum
     27 {
     28     IMG_FORMAT_CI
     29 } img_format;
     30 
     31 rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth)
     32 {
     33     rgba *img;
     34     int img_size;
     35 
     36     img_size = width * height * sizeof(*img);
     37     img = malloc(img_size);
     38     if (!img) {
     39         ERROR("Error allocating %d bytes\n", img_size);
     40         return NULL;
     41     }
     42 
     43     if (depth == 16) {
     44         for (int i = 0; i < width * height; i++) {
     45             img[i].red = SCALE_5_8((raw[i * 2] & 0xF8) >> 3);
     46             img[i].green = SCALE_5_8(((raw[i * 2] & 0x07) << 2) | ((raw[i * 2 + 1] & 0xC0) >> 6));
     47             img[i].blue = SCALE_5_8((raw[i * 2 + 1] & 0x3E) >> 1);
     48             img[i].alpha = (raw[i * 2 + 1] & 0x01) ? 0xFF : 0x00;
     49         }
     50     }
     51     else if (depth == 32) {
     52         for (int i = 0; i < width * height; i++) {
     53             img[i].red = raw[i * 4];
     54             img[i].green = raw[i * 4 + 1];
     55             img[i].blue = raw[i * 4 + 2];
     56             img[i].alpha = raw[i * 4 + 3];
     57         }
     58     }
     59 
     60     return img;
     61 }
     62 
     63 // extract RGBA from CI raw data and palette
     64 rgba *rawci2rgba(const uint8_t *rawci, const uint8_t *palette, int width, int height, int depth)
     65 {
     66     uint8_t *raw_rgba;
     67     rgba *img = NULL;
     68     int raw_size;
     69     int pal_index_1;
     70     int pal_index_2;
     71 
     72     // first convert to raw RGBA
     73     raw_size = 2 * width * height;
     74     raw_rgba = malloc(raw_size);
     75     if (!raw_rgba) {
     76         ERROR("Error allocating %u bytes\n", raw_size);
     77         return NULL;
     78     }
     79 
     80     if (depth == 4) { // CI4
     81         for (int i = 0; i < (width * height) / 2; i++) {
     82             pal_index_1 = rawci[i] >> 4;
     83             pal_index_2 = rawci[i] & 0xF;
     84             raw_rgba[i * 4 + 0] = palette[pal_index_1 * 2 + 0];
     85             raw_rgba[i * 4 + 1] = palette[pal_index_1 * 2 + 1];
     86             raw_rgba[i * 4 + 2] = palette[pal_index_2 * 2 + 0];
     87             raw_rgba[i * 4 + 3] = palette[pal_index_2 * 2 + 1];
     88         }
     89     } else { // CI8
     90         for (int i = 0; i < width * height; i++) {
     91             pal_index_1 = rawci[i];
     92             raw_rgba[i * 2 + 0] = palette[pal_index_1 * 2 + 0];
     93             raw_rgba[i * 2 + 1] = palette[pal_index_1 * 2 + 1];
     94         }
     95     }
     96 
     97     // then convert to RGBA image data
     98     img = raw2rgba(raw_rgba, width, height, 16);
     99 
    100     free(raw_rgba);
    101 
    102     return img;
    103 }
    104 
    105 int rgba2rawci(uint8_t *raw, uint8_t *out_palette, int *pal_len, const rgba *img, int width, int height, int depth)
    106 {
    107     int size = width * height * depth / 8;
    108     int num_colors = *pal_len / 2;
    109 
    110     INFO("Converting RGBA %dx%d to raw CI%d\n", width, height, depth);
    111 
    112     uint8_t *rgba32_palette = malloc(num_colors * 4);
    113     uint8_t *ci8_raw = malloc(width * height);
    114 
    115     // Use ExoQuant to convert the RGBA32 data buffer to an CI8 output
    116     exq_data *pExq = exq_init();
    117     exq_feed(pExq, (uint8_t*)img, width * height);
    118     exq_quantize_hq(pExq, num_colors);
    119     exq_get_palette(pExq, rgba32_palette, num_colors);
    120     exq_map_image_ordered(pExq, width, height, (uint8_t*)img, ci8_raw);
    121     exq_free(pExq);
    122 
    123     if (depth == 4) {
    124         // Convert CI8 image to CI4 image
    125         for (int i = 0; i < size; i++) {
    126             raw[i] = (ci8_raw[i * 2 + 0] << 4) | ci8_raw[i * 2 + 1];
    127         }
    128     }
    129     else {
    130         memcpy(raw, ci8_raw, size);
    131     }
    132 
    133     // Convert RGBA32 palette to RGBA16
    134     for (int i = 0; i < num_colors; i++) {
    135         unsigned char red = (rgba32_palette[i * 4 + 0] / 8) & 0x1F;
    136         unsigned char green = (rgba32_palette[i * 4 + 1] / 8) & 0x1F;
    137         unsigned char blue = (rgba32_palette[i * 4 + 2] / 8) & 0x1F;
    138         unsigned char alpha = rgba32_palette[i * 4 + 3] > 0 ? 1 : 0; // 1 bit alpha
    139 
    140         out_palette[i * 2 + 0] = (red << 3) | (green >> 2);
    141         out_palette[i * 2 + 1] = ((green & 3) << 6) | (blue << 1) | alpha;
    142     }
    143 
    144     free(rgba32_palette);
    145     free(ci8_raw);
    146     return size;
    147 }
    148 
    149 rgba *png2rgba(const char *png_filename, int *width, int *height)
    150 {
    151     rgba *img = NULL;
    152     int w = 0;
    153     int h = 0;
    154     int channels = 0;
    155     int img_size;
    156 
    157     stbi_uc *data = stbi_load(png_filename, &w, &h, &channels, STBI_default);
    158     if (!data || w <= 0 || h <= 0) {
    159         ERROR("Error loading png file \"%s\"\n", png_filename);
    160         return NULL;
    161     }
    162     INFO("Read \"%s\" %dx%d channels: %d\n", png_filename, w, h, channels);
    163 
    164     img_size = w * h * sizeof(*img);
    165     img = malloc(img_size);
    166     if (!img) {
    167         ERROR("Error allocating %u bytes\n", img_size);
    168         return NULL;
    169     }
    170 
    171     switch (channels) {
    172     case 3: // red, green, blue
    173     case 4: // red, green, blue, alpha
    174         for (int j = 0; j < h; j++) {
    175             for (int i = 0; i < w; i++) {
    176                 int idx = j*w + i;
    177                 img[idx].red = data[channels*idx];
    178                 img[idx].green = data[channels*idx + 1];
    179                 img[idx].blue = data[channels*idx + 2];
    180                 if (channels == 4) {
    181                     img[idx].alpha = data[channels*idx + 3];
    182                 }
    183                 else {
    184                     img[idx].alpha = 0xFF;
    185                 }
    186             }
    187         }
    188         break;
    189     case 2: // grey, alpha
    190         for (int j = 0; j < h; j++) {
    191             for (int i = 0; i < w; i++) {
    192                 int idx = j*w + i;
    193                 img[idx].red = data[2 * idx];
    194                 img[idx].green = data[2 * idx];
    195                 img[idx].blue = data[2 * idx];
    196                 img[idx].alpha = data[2 * idx + 1];
    197             }
    198         }
    199         break;
    200     default:
    201         ERROR("Don't know how to read channels: %d\n", channels);
    202         free(img);
    203         img = NULL;
    204     }
    205 
    206     // cleanup
    207     stbi_image_free(data);
    208 
    209     *width = w;
    210     *height = h;
    211     return img;
    212 }
    213 
    214 int rgba2png(const char *png_filename, const rgba *img, int width, int height)
    215 {
    216     int ret = 0;
    217     INFO("Saving RGBA %dx%d to \"%s\"\n", width, height, png_filename);
    218 
    219     // convert to format stb_image_write expects
    220     uint8_t *data = malloc(4 * width*height);
    221     if (data) {
    222         for (int j = 0; j < height; j++) {
    223             for (int i = 0; i < width; i++) {
    224                 int idx = j*width + i;
    225                 data[4 * idx] = img[idx].red;
    226                 data[4 * idx + 1] = img[idx].green;
    227                 data[4 * idx + 2] = img[idx].blue;
    228                 data[4 * idx + 3] = img[idx].alpha;
    229             }
    230         }
    231 
    232         ret = stbi_write_png(png_filename, width, height, 4, data, 0);
    233 
    234         free(data);
    235     }
    236 
    237     return ret;
    238 }
    239 
    240 const char *n64graphics_get_read_version(void)
    241 {
    242     return "stb_image 2.19";
    243 }
    244 
    245 const char *n64graphics_get_write_version(void)
    246 {
    247     return "stb_image_write 1.09";
    248 }
    249 
    250 /***************************************************************/
    251 
    252 #define N64GRAPHICS_VERSION "0.4 - CI Only Branch"
    253 #include <string.h>
    254 
    255 typedef enum
    256 {
    257     MODE_EXPORT,
    258     MODE_IMPORT,
    259 } tool_mode;
    260 
    261 typedef struct
    262 {
    263     char *img_filename;
    264     char *bin_filename;
    265     tool_mode mode;
    266     unsigned int offset;
    267     img_format format;
    268     int depth;
    269     int width;
    270     int height;
    271     int truncate;
    272 } graphics_config;
    273 
    274 static const graphics_config default_config =
    275 {
    276     .img_filename = NULL,
    277     .bin_filename = NULL,
    278     .mode = MODE_EXPORT,
    279     .offset = 0,
    280     .format = IMG_FORMAT_CI,
    281     .depth = 8,
    282     .width = 32,
    283     .height = 32,
    284     .truncate = 1,
    285 };
    286 
    287 typedef struct
    288 {
    289     const char *name;
    290     img_format format;
    291     int depth;
    292 } format_entry;
    293 
    294 static const format_entry format_table[] =
    295 {
    296     { "ci4",    IMG_FORMAT_CI,   4 },
    297     { "ci8",    IMG_FORMAT_CI,   8 },
    298 };
    299 
    300 static const char *format2str(img_format format, int depth)
    301 {
    302     for (unsigned i = 0; i < DIM(format_table); i++) {
    303         if (format == format_table[i].format && depth == format_table[i].depth) {
    304             return format_table[i].name;
    305         }
    306     }
    307     return "unknown";
    308 }
    309 
    310 static int parse_format(graphics_config *config, const char *str)
    311 {
    312     for (unsigned i = 0; i < DIM(format_table); i++) {
    313         if (!strcasecmp(str, format_table[i].name)) {
    314             config->format = format_table[i].format;
    315             config->depth = format_table[i].depth;
    316             return 1;
    317         }
    318     }
    319     return 0;
    320 }
    321 
    322 static void print_usage(void)
    323 {
    324     ERROR("Usage: n64graphics_ci -e/-i BIN_FILE -g PNG_FILE [-o offset] [-f FORMAT] [-w WIDTH] [-h HEIGHT] [-V]\n"
    325         "\n"
    326         "n64graphics v" N64GRAPHICS_VERSION ": N64 graphics manipulator\n"
    327         "\n"
    328         "Required arguments:\n"
    329         " -e BIN_FILE  export from BIN_FILE to PNG_FILE\n"
    330         " -i BIN_FILE  import from PNG_FILE to BIN_FILE\n"
    331         " -g PNG_FILE  graphics file to import/export (.png)\n"
    332         "Optional arguments:\n"
    333         " -o OFFSET    starting offset in BIN_FILE (prevents truncation during import)\n"
    334         " -f FORMAT    texture format: ci4, ci8 (default: %s)\n"
    335         " -w WIDTH     export texture width (default: %d)\n"
    336         " -h HEIGHT    export texture height (default: %d)\n"
    337         " -v           verbose logging\n"
    338         " -V           print version information\n",
    339         format2str(default_config.format, default_config.depth),
    340         default_config.width,
    341         default_config.height);
    342 }
    343 
    344 static void print_version(void)
    345 {
    346     ERROR("n64graphics v" N64GRAPHICS_VERSION ", using:\n"
    347         "  %s\n"
    348         "  %s\n",
    349         n64graphics_get_read_version(), n64graphics_get_write_version());
    350 }
    351 
    352 // parse command line arguments
    353 static int parse_arguments(int argc, char *argv[], graphics_config *config)
    354 {
    355     for (int i = 1; i < argc; i++) {
    356         if (argv[i][0] == '-') {
    357             switch (argv[i][1]) {
    358             case 'e':
    359                 if (++i >= argc) return 0;
    360                 config->bin_filename = argv[i];
    361                 config->mode = MODE_EXPORT;
    362                 break;
    363             case 'f':
    364                 if (++i >= argc) return 0;
    365                 if (!parse_format(config, argv[i])) {
    366                     return 0;
    367                 }
    368                 break;
    369             case 'i':
    370                 if (++i >= argc) return 0;
    371                 config->bin_filename = argv[i];
    372                 config->mode = MODE_IMPORT;
    373                 break;
    374             case 'g':
    375                 if (++i >= argc) return 0;
    376                 config->img_filename = argv[i];
    377                 break;
    378             case 'h':
    379                 if (++i >= argc) return 0;
    380                 config->height = strtoul(argv[i], NULL, 0);
    381                 break;
    382             case 'o':
    383                 if (++i >= argc) return 0;
    384                 config->offset = strtoul(argv[i], NULL, 0);
    385                 config->truncate = 0;
    386                 break;
    387             case 'w':
    388                 if (++i >= argc) return 0;
    389                 config->width = strtoul(argv[i], NULL, 0);
    390                 break;
    391             case 'v':
    392                 g_verbosity = 1;
    393                 break;
    394             case 'V':
    395                 print_version();
    396                 exit(0);
    397                 break;
    398             default:
    399                 return 0;
    400                 break;
    401             }
    402         }
    403         else {
    404             return 0;
    405         }
    406     }
    407     return 1;
    408 }
    409 
    410 char* getPaletteFilename(char* ci_filename)
    411 {
    412     int bin_filename_len;
    413     int extension_len;
    414     char* pal_bin_filename;
    415     char* extension_loc;
    416     char* extension;
    417 
    418     // Write Palette file
    419     bin_filename_len = strlen(ci_filename);
    420     extension_loc = strrchr(ci_filename, '.');
    421     extension_len = (int)(extension_loc - ci_filename);
    422     extension = malloc(extension_len);
    423     memcpy(extension, extension_loc, extension_len);
    424     pal_bin_filename = malloc(bin_filename_len + 4); // +4 to include ".pal"
    425     if (!pal_bin_filename) {
    426         ERROR("Error allocating bytes for palette filename\n");
    427     }
    428     memcpy(pal_bin_filename, ci_filename, bin_filename_len);
    429     strcpy(pal_bin_filename + bin_filename_len, ".pal");
    430     //strcpy(pal_bin_filename + bin_filename_len, extension);
    431 
    432     free(extension);
    433 
    434     return pal_bin_filename;
    435 }
    436 
    437 int main(int argc, char* argv[])
    438 {
    439     graphics_config config = default_config;
    440     rgba *imgr;
    441     FILE *fp;
    442     uint8_t *raw;
    443     int raw_size;
    444     int length = 0;
    445     int flength;
    446     int res;
    447     FILE *fp_pal; // for ci palette
    448     uint8_t *pal;
    449     int pal_len;
    450     char* pal_bin_filename;
    451 
    452     int valid = parse_arguments(argc, argv, &config);
    453     if (!valid || !config.bin_filename || !config.bin_filename || !config.img_filename) {
    454         print_usage();
    455         exit(EXIT_FAILURE);
    456     }
    457 
    458     if (config.mode == MODE_IMPORT) {
    459         printf("%s\n", config.bin_filename);
    460         if (config.truncate) {
    461             fp = fopen(config.bin_filename, "wb");
    462         }
    463         else {
    464             fp = fopen(config.bin_filename, "r+b");
    465         }
    466         if (!fp) {
    467             ERROR("Error opening binary file \"%s\"\n", config.bin_filename);
    468             return -1;
    469         }
    470         if (!config.truncate) {
    471             fseek(fp, config.offset, SEEK_SET);
    472         }
    473         switch (config.format) {
    474         case IMG_FORMAT_CI:
    475             imgr = png2rgba(config.img_filename, &config.width, &config.height);
    476             raw_size = config.width * config.height * config.depth / 8;
    477             raw = malloc(raw_size);
    478             if (!raw) {
    479                 ERROR("Error allocating %u bytes\n", raw_size);
    480             }
    481             if (config.depth == 4) {
    482                 pal_len = 16 * 2; // CI4
    483             }
    484             else {
    485                 pal_len = 256 * 2; // CI8
    486             }
    487             pal = malloc(pal_len);
    488             if (!pal) {
    489                 ERROR("Error allocating %u bytes for palette\n", pal_len);
    490             }
    491             length = rgba2rawci(raw, pal, &pal_len, imgr, config.width, config.height, config.depth);
    492             //length = rgba2raw(raw, imgr, config.width, config.height, config.depth);
    493             break;
    494         }
    495         if (length <= 0) {
    496             ERROR("Error converting to raw format\n");
    497             return EXIT_FAILURE;
    498         }
    499         INFO("Writing 0x%X bytes to offset 0x%X of \"%s\"\n", length, config.offset, config.bin_filename);
    500         flength = fwrite(raw, 1, length, fp);
    501         if (flength != length) {
    502             ERROR("Error writing %d bytes to \"%s\"\n", length, config.bin_filename);
    503         }
    504         fclose(fp);
    505 
    506         if (config.format == IMG_FORMAT_CI) {
    507             pal_bin_filename = getPaletteFilename(config.bin_filename);
    508 
    509             fp_pal = fopen(pal_bin_filename, "wb");
    510             INFO("Writing 0x%X bytes to palette file \"%s\"\n", pal_len, pal_bin_filename);
    511             flength = fwrite(pal, 1, pal_len, fp_pal);
    512             if (flength != pal_len) {
    513                 ERROR("Error writing %d bytes to \"%s\"\n", pal_len, pal_bin_filename);
    514             }
    515             fclose(fp_pal);
    516             free(pal_bin_filename);
    517         }
    518     } else { // Export
    519         if (config.width <= 0 || config.height <= 0 || config.depth <= 0) {
    520             ERROR("Error: must set position width and height for export\n");
    521             return EXIT_FAILURE;
    522         }
    523         fp = fopen(config.bin_filename, "rb");
    524         if (!fp) {
    525             ERROR("Error opening \"%s\"\n", config.bin_filename);
    526             return -1;
    527         }
    528         raw_size = config.width * config.height * config.depth / 8;
    529         raw = malloc(raw_size);
    530         if (config.offset > 0) {
    531             fseek(fp, config.offset, SEEK_SET);
    532         }
    533         flength = fread(raw, 1, raw_size, fp);
    534         if (flength != raw_size) {
    535             ERROR("Error reading %d bytes from \"%s\"\n", raw_size, config.bin_filename);
    536         }
    537         switch (config.format) {
    538         case IMG_FORMAT_CI:
    539             // Read Palette file
    540             pal_bin_filename = getPaletteFilename(config.bin_filename);
    541             fp_pal = fopen(pal_bin_filename, "rb");
    542             if (!fp_pal) {
    543                 ERROR("Error opening \"%s\"\n", pal_bin_filename);
    544                 return -1;
    545             }
    546             if (config.depth == 4) {
    547                 pal_len = 16 * 2; // CI4
    548             }
    549             else {
    550                 pal_len = 256 * 2; // CI8
    551             }
    552 
    553             pal = malloc(pal_len);
    554             if (config.offset > 0) {
    555                 fseek(fp, config.offset, SEEK_SET);
    556             }
    557             flength = fread(pal, 1, pal_len, fp_pal);
    558 
    559             imgr = rawci2rgba(raw, pal, config.width, config.height, config.depth);
    560             res = rgba2png(config.img_filename, imgr, config.width, config.height);
    561 
    562             free(pal_bin_filename);
    563             break;
    564         default:
    565             return EXIT_FAILURE;
    566         }
    567         if (!res) {
    568             ERROR("Error writing to \"%s\"\n", config.img_filename);
    569         }
    570     }
    571 
    572     return 0;
    573 }